From d13292b8f64eac639f40836bc0285fdf90dfca94 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 18 May 2021 02:46:33 -0400 Subject: [PATCH] import sudo-1.8.29-7.el8 --- SOURCES/sudo-1.9.5-CVE-2021-23239.patch | 61 +++ SOURCES/sudo-1.9.5-CVE-2021-23240-1.patch | 158 ++++++++ SOURCES/sudo-1.9.5-CVE-2021-23240-2.patch | 431 ++++++++++++++++++++++ SOURCES/sudo-1.9.5-CVE-2021-23240-3.patch | 345 +++++++++++++++++ SOURCES/sudo-1.9.5-CVE-2021-23240-4.patch | 380 +++++++++++++++++++ SOURCES/sudo-1.9.5-CVE-2021-23240-5.patch | 47 +++ SPECS/sudo.spec | 34 +- 7 files changed, 1450 insertions(+), 6 deletions(-) create mode 100644 SOURCES/sudo-1.9.5-CVE-2021-23239.patch create mode 100644 SOURCES/sudo-1.9.5-CVE-2021-23240-1.patch create mode 100644 SOURCES/sudo-1.9.5-CVE-2021-23240-2.patch create mode 100644 SOURCES/sudo-1.9.5-CVE-2021-23240-3.patch create mode 100644 SOURCES/sudo-1.9.5-CVE-2021-23240-4.patch create mode 100644 SOURCES/sudo-1.9.5-CVE-2021-23240-5.patch diff --git a/SOURCES/sudo-1.9.5-CVE-2021-23239.patch b/SOURCES/sudo-1.9.5-CVE-2021-23239.patch new file mode 100644 index 0000000..2ff50fd --- /dev/null +++ b/SOURCES/sudo-1.9.5-CVE-2021-23239.patch @@ -0,0 +1,61 @@ +From db1f27c0350e9e437c93780ffe88648ae1984467 Mon Sep 17 00:00:00 2001 +From: "Todd C. Miller" +Date: Wed, 6 Jan 2021 10:16:00 -0700 +Subject: [PATCH] Fix potential directory existing info leak in sudoedit. When + creating a new file, sudoedit checks to make sure the parent directory exists + so it can provide the user with a sensible error message. However, this + could be used to test for the existence of directories not normally + accessible to the user by pointing to them with a symbolic link when the + parent directory is controlled by the user. Problem reported by Matthias + Gerstner of SUSE. + +--- + src/sudo_edit.c | 29 ++++++++++++++++++++++++----- + 1 file changed, 24 insertions(+), 5 deletions(-) + +diff --git a/src/sudo_edit.c b/src/sudo_edit.c +index 82e04a71b..5502b7bd9 100644 +--- a/src/sudo_edit.c ++++ b/src/sudo_edit.c +@@ -541,14 +541,33 @@ sudo_edit_create_tfiles(struct command_details *command_details, + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details); + if (ofd != -1 || errno == ENOENT) { + if (ofd == -1) { +- /* New file, verify parent dir exists unless in cwd. */ ++ /* ++ * New file, verify parent dir exists unless in cwd. ++ * This fails early so the user knows ahead of time if the ++ * edit won't succeed. Additional checks are performed ++ * when copying the temporary file back to the origin. ++ */ + char *slash = strrchr(files[i], '/'); + if (slash != NULL && slash != files[i]) { +- int serrno = errno; ++ const int sflags = command_details->flags; ++ const int serrno = errno; ++ int dfd; ++ ++ /* ++ * The parent directory is allowed to be a symbolic ++ * link as long as *its* parent is not writable. ++ */ + *slash = '\0'; +- if (stat(files[i], &sb) == 0 && S_ISDIR(sb.st_mode)) { +- memset(&sb, 0, sizeof(sb)); +- rc = 0; ++ SET(command_details->flags, CD_SUDOEDIT_FOLLOW); ++ dfd = sudo_edit_open(files[i], DIR_OPEN_FLAGS, ++ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details); ++ command_details->flags = sflags; ++ if (dfd != -1) { ++ if (fstat(dfd, &sb) == 0 && S_ISDIR(sb.st_mode)) { ++ memset(&sb, 0, sizeof(sb)); ++ rc = 0; ++ } ++ close(dfd); + } + *slash = '/'; + errno = serrno; +-- +2.26.2 + diff --git a/SOURCES/sudo-1.9.5-CVE-2021-23240-1.patch b/SOURCES/sudo-1.9.5-CVE-2021-23240-1.patch new file mode 100644 index 0000000..02efa86 --- /dev/null +++ b/SOURCES/sudo-1.9.5-CVE-2021-23240-1.patch @@ -0,0 +1,158 @@ +From adb4360c40df99238c17c3ecedcb1d32d76e2b2e Mon Sep 17 00:00:00 2001 +From: "Todd C. Miller" +Date: Fri, 17 Apr 2020 19:08:56 -0600 +Subject: [PATCH] Extend the original file before to the new size before + updating it. Instead of opening the original file for writing w/ tuncation, + we first extend the file with zeroes (by writing, not seeking), then + overwrite it. This should allow sudo to fail early if the disk is out of + space before it overwrites the original file. + +--- + src/sudo_edit.c | 93 ++++++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 77 insertions(+), 16 deletions(-) + +diff --git a/src/sudo_edit.c b/src/sudo_edit.c +index 28f6c6100..d99a5658a 100644 +--- a/src/sudo_edit.c ++++ b/src/sudo_edit.c +@@ -1,7 +1,7 @@ + /* + * SPDX-License-Identifier: ISC + * +- * Copyright (c) 2004-2008, 2010-2018 Todd C. Miller ++ * Copyright (c) 2004-2008, 2010-2020 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 +@@ -650,6 +650,51 @@ sudo_edit_create_tfiles(struct command_details *command_details, + debug_return_int(j); + } + ++/* ++ * Extend the given fd to the specified size in bytes. ++ * We do this to allocate disk space up-front before overwriting ++ * the original file with the temporary. Otherwise, we could ++ * we run out of disk space after truncating the original file. ++ */ ++static int ++sudo_edit_extend_file(int fd, off_t new_size) ++{ ++ off_t old_size, size; ++ ssize_t nwritten; ++ char zeroes[1024] = { '\0' }; ++ debug_decl(sudo_edit_extend_file, SUDO_DEBUG_EDIT); ++ ++ if ((old_size = lseek(fd, 0, SEEK_END)) == -1) { ++ sudo_warn("lseek"); ++ debug_return_int(-1); ++ } ++ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: extending file from %lld to %lld", ++ __func__, (long long)old_size, (long long)new_size); ++ ++ for (size = old_size; size < new_size; size += nwritten) { ++ size_t len = new_size - size; ++ if (len > sizeof(zeroes)) ++ len = sizeof(zeroes); ++ nwritten = write(fd, zeroes, len); ++ if (nwritten == -1) { ++ int serrno = errno; ++ if (ftruncate(fd, old_size) == -1) { ++ sudo_debug_printf( ++ SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, ++ "unable to truncate to %lld", (long long)old_size); ++ } ++ errno = serrno; ++ debug_return_int(-1); ++ } ++ } ++ if (lseek(fd, 0, SEEK_SET) == -1) { ++ sudo_warn("lseek"); ++ debug_return_int(-1); ++ } ++ ++ debug_return_int(0); ++} ++ + /* + * Copy the temporary files specified in tf to the originals. + * Returns the number of copy errors or 0 if completely successful. +@@ -708,38 +753,53 @@ sudo_edit_copy_tfiles(struct command_details *command_details, + switch_user(command_details->euid, command_details->egid, + command_details->ngroups, command_details->groups); + oldmask = umask(command_details->umask); +- ofd = sudo_edit_open(tf[i].ofile, O_WRONLY|O_TRUNC|O_CREAT, ++ ofd = sudo_edit_open(tf[i].ofile, O_WRONLY|O_CREAT, + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details); + umask(oldmask); + switch_user(ROOT_UID, user_details.egid, + user_details.ngroups, user_details.groups); +- if (ofd == -1) { +- sudo_warn(U_("unable to write to %s"), tf[i].ofile); +- sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile); +- close(tfd); +- errors++; +- continue; ++ if (ofd == -1) ++ goto write_error; ++ /* Extend the file to the new size if larger before copying. */ ++ if (tf[i].osize > 0 && sb.st_size > tf[i].osize) { ++ if (sudo_edit_extend_file(ofd, sb.st_size) == -1) ++ goto write_error; + } ++ /* Overwrite the old file with the new contents. */ + while ((nread = read(tfd, buf, sizeof(buf))) > 0) { +- if ((nwritten = write(ofd, buf, nread)) != nread) { ++ ssize_t off = 0; ++ do { ++ nwritten = write(ofd, buf + off, nread - off); + if (nwritten == -1) +- sudo_warn("%s", tf[i].ofile); +- else +- sudo_warnx(U_("%s: short write"), tf[i].ofile); +- break; +- } ++ goto write_error; ++ off += nwritten; ++ } while (nread > off); + } + if (nread == 0) { +- /* success, got EOF */ ++ /* success, read to EOF */ ++ if (tf[i].osize > 0 && sb.st_size < tf[i].osize) { ++ /* We don't open with O_TRUNC so must truncate manually. */ ++ if (ftruncate(ofd, sb.st_size) == -1) { ++ sudo_debug_printf( ++ SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, ++ "unable to truncate %s to %lld", tf[i].ofile, ++ (long long)sb.st_size); ++ goto write_error; ++ } ++ } + unlink(tf[i].tfile); + } else if (nread < 0) { + sudo_warn(U_("unable to read temporary file")); + sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile); ++ errors++; + } else { ++write_error: + sudo_warn(U_("unable to write to %s"), tf[i].ofile); + sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile); ++ errors++; + } +- close(ofd); ++ if (ofd != -1) ++ close(ofd); + close(tfd); + } + debug_return_int(errors); +@@ -1065,6 +1125,7 @@ cleanup: + for (i = 0; i < nfiles; i++) { + if (tf[i].tfile != NULL) + unlink(tf[i].tfile); ++ free(tf[i].tfile); + } + } + free(tf); +-- +2.26.2 + diff --git a/SOURCES/sudo-1.9.5-CVE-2021-23240-2.patch b/SOURCES/sudo-1.9.5-CVE-2021-23240-2.patch new file mode 100644 index 0000000..1b40774 --- /dev/null +++ b/SOURCES/sudo-1.9.5-CVE-2021-23240-2.patch @@ -0,0 +1,431 @@ +diff -up ./src/copy_file.c.symbolic-link-attack-2 ./src/copy_file.c +--- ./src/copy_file.c.symbolic-link-attack-2 2021-02-02 15:31:20.555340446 +0100 ++++ ./src/copy_file.c 2021-02-02 15:31:20.555340446 +0100 +@@ -0,0 +1,128 @@ ++/* ++ * SPDX-License-Identifier: ISC ++ * ++ * Copyright (c) 2020 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 ++#include ++ ++#include "sudo.h" ++ ++/* ++ * Extend the given fd to the specified size in bytes. ++ * We do this to allocate disk space up-front before overwriting ++ * the original file with the temporary. Otherwise, we could ++ * we run out of disk space after truncating the original file. ++ */ ++static int ++sudo_extend_file(int fd, const char *name, off_t new_size) ++{ ++ off_t old_size, size; ++ ssize_t nwritten; ++ char zeroes[BUFSIZ] = { '\0' }; ++ debug_decl(sudo_extend_file, SUDO_DEBUG_UTIL); ++ ++ if ((old_size = lseek(fd, 0, SEEK_END)) == -1) { ++ sudo_warn("lseek"); ++ debug_return_int(-1); ++ } ++ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: extending %s from %lld to %lld", ++ __func__, name, (long long)old_size, (long long)new_size); ++ ++ for (size = old_size; size < new_size; size += nwritten) { ++ size_t len = new_size - size; ++ if (len > sizeof(zeroes)) ++ len = sizeof(zeroes); ++ nwritten = write(fd, zeroes, len); ++ if (nwritten == -1) { ++ int serrno = errno; ++ if (ftruncate(fd, old_size) == -1) { ++ sudo_debug_printf( ++ SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, ++ "unable to truncate %s to %lld", name, (long long)old_size); ++ } ++ errno = serrno; ++ debug_return_int(-1); ++ } ++ } ++ if (lseek(fd, 0, SEEK_SET) == -1) { ++ sudo_warn("lseek"); ++ debug_return_int(-1); ++ } ++ ++ debug_return_int(0); ++} ++ ++/* ++ * Copy the contents of src_fd into dst_fd. ++ * Returns 0 on success or -1 on error. ++ */ ++int ++sudo_copy_file(const char *src, int src_fd, off_t src_len, const char *dst, ++ int dst_fd, off_t dst_len) ++{ ++ char buf[BUFSIZ]; ++ ssize_t nwritten, nread; ++ debug_decl(sudo_copy_file, SUDO_DEBUG_UTIL); ++ ++ /* Extend the file to the new size if larger before copying. */ ++ if (dst_len > 0 && src_len > dst_len) { ++ if (sudo_extend_file(dst_fd, dst, src_len) == -1) ++ goto write_error; ++ } ++ ++ /* Overwrite the old file with the new contents. */ ++ while ((nread = read(src_fd, buf, sizeof(buf))) > 0) { ++ ssize_t off = 0; ++ do { ++ nwritten = write(dst_fd, buf + off, nread - off); ++ if (nwritten == -1) ++ goto write_error; ++ off += nwritten; ++ } while (nread > off); ++ } ++ if (nread == 0) { ++ /* success, read to EOF */ ++ if (src_len < dst_len) { ++ /* We don't open with O_TRUNC so must truncate manually. */ ++ if (ftruncate(dst_fd, src_len) == -1) { ++ sudo_debug_printf( ++ SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, ++ "unable to truncate %s to %lld", dst, (long long)src_len); ++ goto write_error; ++ } ++ } ++ debug_return_int(0); ++ } else if (nread < 0) { ++ sudo_warn(U_("unable to read from %s"), src); ++ debug_return_int(-1); ++ } else { ++write_error: ++ sudo_warn(U_("unable to write to %s"), dst); ++ debug_return_int(-1); ++ } ++} +diff -up ./src/Makefile.in.symbolic-link-attack-2 ./src/Makefile.in +--- ./src/Makefile.in.symbolic-link-attack-2 2019-10-28 13:28:54.000000000 +0100 ++++ ./src/Makefile.in 2021-02-02 15:31:20.555340446 +0100 +@@ -120,16 +120,17 @@ SHELL = @SHELL@ + + PROGS = @PROGS@ + +-OBJS = conversation.o env_hooks.o exec.o exec_common.o exec_monitor.o \ +- exec_nopty.o exec_pty.o get_pty.o hooks.o limits.o load_plugins.o \ +- net_ifs.o parse_args.o preserve_fds.o signal.o sudo.o sudo_edit.o \ +- tcsetpgrp_nobg.o tgetpass.o ttyname.o utmp.o @SUDO_OBJS@ ++OBJS = conversation.o copy_file.o env_hooks.o exec.o exec_common.o \ ++ exec_monitor.o exec_nopty.o exec_pty.o get_pty.o hooks.o \ ++ limits.o load_plugins.o net_ifs.o parse_args.o preserve_fds.o \ ++ signal.o sudo.o sudo_edit.o tcsetpgrp_nobg.o tgetpass.o \ ++ ttyname.o utmp.o @SUDO_OBJS@ + + IOBJS = $(OBJS:.o=.i) sesh.i + + POBJS = $(IOBJS:.i=.plog) + +-SESH_OBJS = sesh.o exec_common.o ++SESH_OBJS = copy_file.o exec_common.o sesh.o + + CHECK_NOEXEC_OBJS = check_noexec.o exec_common.o + +@@ -335,6 +336,22 @@ conversation.i: $(srcdir)/conversation.c + $(CC) -E -o $@ $(CPPFLAGS) $< + conversation.plog: conversation.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/conversation.c --i-file $< --output-file $@ ++copy_file.o: $(srcdir)/copy_file.c $(incdir)/compat/stdbool.h \ ++ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ ++ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \ ++ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ ++ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo.h \ ++ $(top_builddir)/config.h $(top_builddir)/pathnames.h ++ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/copy_file.c ++copy_file.i: $(srcdir)/copy_file.c $(incdir)/compat/stdbool.h \ ++ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ ++ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \ ++ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ ++ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo.h \ ++ $(top_builddir)/config.h $(top_builddir)/pathnames.h ++ $(CC) -E -o $@ $(CPPFLAGS) $< ++copy_file.plog: copy_file.i ++ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/copy_file.c --i-file $< --output-file $@ + env_hooks.o: $(srcdir)/env_hooks.c $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ + $(incdir)/sudo_debug.h $(incdir)/sudo_dso.h \ +diff -up ./src/sesh.c.symbolic-link-attack-2 ./src/sesh.c +--- ./src/sesh.c.symbolic-link-attack-2 2019-10-28 13:28:52.000000000 +0100 ++++ ./src/sesh.c 2021-02-02 15:31:20.555340446 +0100 +@@ -1,7 +1,7 @@ + /* + * SPDX-License-Identifier: ISC + * +- * Copyright (c) 2008, 2010-2018 Todd C. Miller ++ * Copyright (c) 2008, 2010-2018, 2020 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 +@@ -182,7 +182,7 @@ sesh_sudoedit(int argc, char *argv[]) + * so that it's ensured that the temporary files are + * created by us and that we are not opening any symlinks. + */ +- oflags_dst = O_WRONLY|O_TRUNC|O_CREAT|(post ? follow : O_EXCL); ++ oflags_dst = O_WRONLY|O_CREAT|(post ? follow : O_EXCL); + for (i = 0; i < argc - 1; i += 2) { + const char *path_src = argv[i]; + const char *path_dst = argv[i + 1]; +@@ -214,14 +214,29 @@ sesh_sudoedit(int argc, char *argv[]) + } + + if (fd_src != -1) { +- while ((nread = read(fd_src, buf, sizeof(buf))) > 0) { +- if ((nwritten = write(fd_dst, buf, nread)) != nread) { +- sudo_warn("%s", path_src); +- if (post) { +- ret = SESH_ERR_SOME_FILES; +- goto nocleanup; +- } else +- goto cleanup_0; ++ off_t len_src = -1; ++ off_t len_dst = -1; ++ ++ if (post) { ++ if (fstat(fd_src, &sb) != 0) { ++ ret = SESH_ERR_SOME_FILES; ++ goto nocleanup; ++ } ++ len_src = sb.st_size; ++ if (fstat(fd_dst, &sb) != 0) { ++ ret = SESH_ERR_SOME_FILES; ++ goto nocleanup; ++ } ++ len_dst = sb.st_size; ++ } ++ ++ if (sudo_copy_file(path_src, fd_src, len_src, path_dst, fd_dst, ++ len_dst) == -1) { ++ if (post) { ++ ret = SESH_ERR_SOME_FILES; ++ goto nocleanup; ++ } else { ++ goto cleanup_0; + } + } + } +diff -up ./src/sudo_edit.c.symbolic-link-attack-2 ./src/sudo_edit.c +--- ./src/sudo_edit.c.symbolic-link-attack-2 2021-02-02 15:31:20.554340459 +0100 ++++ ./src/sudo_edit.c 2021-02-02 15:31:54.355884326 +0100 +@@ -42,7 +42,6 @@ + #include + #include + #include +-#include + #include + + #include "sudo.h" +@@ -551,8 +550,6 @@ sudo_edit_create_tfiles(struct command_d + struct tempfile *tf, char *files[], int nfiles) + { + int i, j, tfd, ofd, rc; +- char buf[BUFSIZ]; +- ssize_t nwritten, nread; + struct timespec times[2]; + struct stat sb; + debug_decl(sudo_edit_create_tfiles, SUDO_DEBUG_EDIT) +@@ -648,18 +645,7 @@ sudo_edit_create_tfiles(struct command_d + debug_return_int(-1); + } + if (ofd != -1) { +- while ((nread = read(ofd, buf, sizeof(buf))) > 0) { +- if ((nwritten = write(tfd, buf, nread)) != nread) { +- if (nwritten == -1) +- sudo_warn("%s", tf[j].tfile); +- else +- sudo_warnx(U_("%s: short write"), tf[j].tfile); +- break; +- } +- } +- if (nread != 0) { +- if (nread < 0) +- sudo_warn("%s", files[i]); ++ if (sudo_copy_file(tf[j].ofile, ofd, tf[j].osize, tf[j].tfile, tfd, -1) == -1) { + close(ofd); + close(tfd); + debug_return_int(-1); +@@ -689,51 +675,6 @@ sudo_edit_create_tfiles(struct command_d + } + + /* +- * Extend the given fd to the specified size in bytes. +- * We do this to allocate disk space up-front before overwriting +- * the original file with the temporary. Otherwise, we could +- * we run out of disk space after truncating the original file. +- */ +-static int +-sudo_edit_extend_file(int fd, off_t new_size) +-{ +- off_t old_size, size; +- ssize_t nwritten; +- char zeroes[1024] = { '\0' }; +- debug_decl(sudo_edit_extend_file, SUDO_DEBUG_EDIT); +- +- if ((old_size = lseek(fd, 0, SEEK_END)) == -1) { +- sudo_warn("lseek"); +- debug_return_int(-1); +- } +- sudo_debug_printf(SUDO_DEBUG_INFO, "%s: extending file from %lld to %lld", +- __func__, (long long)old_size, (long long)new_size); +- +- for (size = old_size; size < new_size; size += nwritten) { +- size_t len = new_size - size; +- if (len > sizeof(zeroes)) +- len = sizeof(zeroes); +- nwritten = write(fd, zeroes, len); +- if (nwritten == -1) { +- int serrno = errno; +- if (ftruncate(fd, old_size) == -1) { +- sudo_debug_printf( +- SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, +- "unable to truncate to %lld", (long long)old_size); +- } +- errno = serrno; +- debug_return_int(-1); +- } +- } +- if (lseek(fd, 0, SEEK_SET) == -1) { +- sudo_warn("lseek"); +- debug_return_int(-1); +- } +- +- debug_return_int(0); +-} +- +-/* + * Copy the temporary files specified in tf to the originals. + * Returns the number of copy errors or 0 if completely successful. + */ +@@ -741,9 +682,7 @@ static int + sudo_edit_copy_tfiles(struct command_details *command_details, + struct tempfile *tf, int nfiles, struct timespec *times) + { +- int i, tfd, ofd, rc, errors = 0; +- char buf[BUFSIZ]; +- ssize_t nwritten, nread; ++ int i, tfd, ofd, errors = 0; + struct timespec ts; + struct stat sb; + mode_t oldmask; +@@ -751,7 +690,7 @@ sudo_edit_copy_tfiles(struct command_det + + /* Copy contents of temp files to real ones. */ + for (i = 0; i < nfiles; i++) { +- rc = -1; ++ int rc = -1; + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "seteuid(%u)", (unsigned int)user_details.uid); + if (seteuid(user_details.uid) != 0) +@@ -764,8 +703,8 @@ sudo_edit_copy_tfiles(struct command_det + "seteuid(%u)", ROOT_UID); + if (seteuid(ROOT_UID) != 0) + sudo_fatal("seteuid(ROOT_UID)"); +- if (rc || !S_ISREG(sb.st_mode)) { +- if (rc) ++ if (rc == -1 || !S_ISREG(sb.st_mode)) { ++ if (rc == -1) + sudo_warn("%s", tf[i].tfile); + else + sudo_warnx(U_("%s: not a regular file"), tf[i].tfile); +@@ -796,46 +735,19 @@ sudo_edit_copy_tfiles(struct command_det + umask(oldmask); + switch_user(ROOT_UID, user_details.egid, + user_details.ngroups, user_details.groups); +- if (ofd == -1) +- goto write_error; +- /* Extend the file to the new size if larger before copying. */ +- if (tf[i].osize > 0 && sb.st_size > tf[i].osize) { +- if (sudo_edit_extend_file(ofd, sb.st_size) == -1) +- goto write_error; ++ if (ofd == -1) { ++ sudo_warn(U_("unable to write to %s"), tf[i].ofile); ++ goto bad; + } ++ + /* Overwrite the old file with the new contents. */ +- while ((nread = read(tfd, buf, sizeof(buf))) > 0) { +- ssize_t off = 0; +- do { +- nwritten = write(ofd, buf + off, nread - off); +- if (nwritten == -1) +- goto write_error; +- off += nwritten; +- } while (nread > off); +- } +- if (nread == 0) { +- /* success, read to EOF */ +- if (tf[i].osize > 0 && sb.st_size < tf[i].osize) { +- /* We don't open with O_TRUNC so must truncate manually. */ +- if (ftruncate(ofd, sb.st_size) == -1) { +- sudo_debug_printf( +- SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, +- "unable to truncate %s to %lld", tf[i].ofile, +- (long long)sb.st_size); +- goto write_error; +- } +- } +- unlink(tf[i].tfile); +- } else if (nread < 0) { +- sudo_warn(U_("unable to read temporary file")); +- sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile); +- errors++; +- } else { +-write_error: +- sudo_warn(U_("unable to write to %s"), tf[i].ofile); ++ if (sudo_copy_file(tf[i].tfile, tfd, sb.st_size, tf[i].ofile, ofd, ++ tf[i].osize) == -1) { ++bad: + sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile); + errors++; + } ++ + if (ofd != -1) + close(ofd); + close(tfd); +diff -up ./src/sudo_exec.h.symbolic-link-attack-2 ./src/sudo_exec.h +--- ./src/sudo_exec.h.symbolic-link-attack-2 2019-10-28 13:27:39.000000000 +0100 ++++ ./src/sudo_exec.h 2021-02-02 15:31:20.556340432 +0100 +@@ -84,6 +84,9 @@ + struct command_details; + struct command_status; + ++/* copy_file.c */ ++int sudo_copy_file(const char *src, int src_fd, off_t src_len, const char *dst, int dst_fd, off_t dst_len); ++ + /* exec.c */ + void exec_cmnd(struct command_details *details, int errfd); + void terminate_command(pid_t pid, bool use_pgrp); diff --git a/SOURCES/sudo-1.9.5-CVE-2021-23240-3.patch b/SOURCES/sudo-1.9.5-CVE-2021-23240-3.patch new file mode 100644 index 0000000..8e4ae34 --- /dev/null +++ b/SOURCES/sudo-1.9.5-CVE-2021-23240-3.patch @@ -0,0 +1,345 @@ +diff -up ./src/exec_monitor.c.symbolic-link-attack-3 ./src/exec_monitor.c +--- ./src/exec_monitor.c.symbolic-link-attack-3 2019-10-28 13:27:39.000000000 +0100 ++++ ./src/exec_monitor.c 2021-02-02 17:11:32.382020407 +0100 +@@ -613,7 +613,7 @@ exec_monitor(struct command_details *det + #ifdef HAVE_SELINUX + if (ISSET(details->flags, CD_RBAC_ENABLED)) { + if (selinux_setup(details->selinux_role, details->selinux_type, +- details->tty, io_fds[SFD_SLAVE]) == -1) ++ details->tty, io_fds[SFD_SLAVE], true) == -1) + goto bad; + } + #endif +diff -up ./src/exec_nopty.c.symbolic-link-attack-3 ./src/exec_nopty.c +--- ./src/exec_nopty.c.symbolic-link-attack-3 2019-10-28 13:27:39.000000000 +0100 ++++ ./src/exec_nopty.c 2021-02-02 17:11:32.382020407 +0100 +@@ -381,7 +381,7 @@ exec_nopty(struct command_details *detai + #ifdef HAVE_SELINUX + if (ISSET(details->flags, CD_RBAC_ENABLED)) { + if (selinux_setup(details->selinux_role, details->selinux_type, +- details->tty, -1) == -1) { ++ details->tty, -1, true) == -1) { + cstat->type = CMD_ERRNO; + cstat->val = errno; + debug_return; +diff -up ./src/selinux.c.symbolic-link-attack-3 ./src/selinux.c +--- ./src/selinux.c.symbolic-link-attack-3 2019-10-28 13:27:39.000000000 +0100 ++++ ./src/selinux.c 2021-02-02 17:11:32.382020407 +0100 +@@ -363,7 +363,7 @@ bad: + */ + int + selinux_setup(const char *role, const char *type, const char *ttyn, +- int ptyfd) ++ int ptyfd, bool label_tty) + { + int ret = -1; + debug_decl(selinux_setup, SUDO_DEBUG_SELINUX) +@@ -392,7 +392,7 @@ selinux_setup(const char *role, const ch + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: new context %s", __func__, + se_state.new_context); + +- if (relabel_tty(ttyn, ptyfd) == -1) { ++ if (label_tty && relabel_tty(ttyn, ptyfd) == -1) { + sudo_warn(U_("unable to set tty context to %s"), se_state.new_context); + goto done; + } +@@ -408,6 +408,28 @@ done: + debug_return_int(ret); + } + ++int ++selinux_setcon(void) ++{ ++ debug_decl(selinux_setcon, SUDO_DEBUG_SELINUX); ++ ++ if (setexeccon(se_state.new_context)) { ++ sudo_warn(U_("unable to set exec context to %s"), se_state.new_context); ++ if (se_state.enforcing) ++ debug_return_int(-1); ++ } ++ ++#ifdef HAVE_SETKEYCREATECON ++ if (setkeycreatecon(se_state.new_context)) { ++ sudo_warn(U_("unable to set key creation context to %s"), se_state.new_context); ++ if (se_state.enforcing) ++ debug_return_int(-1); ++ } ++#endif /* HAVE_SETKEYCREATECON */ ++ ++ debug_return_int(0); ++} ++ + void + selinux_execve(int fd, const char *path, char *const argv[], char *envp[], + bool noexec) +@@ -424,19 +446,9 @@ selinux_execve(int fd, const char *path, + debug_return; + } + +- if (setexeccon(se_state.new_context)) { +- sudo_warn(U_("unable to set exec context to %s"), se_state.new_context); +- if (se_state.enforcing) +- debug_return; +- } +- +-#ifdef HAVE_SETKEYCREATECON +- if (setkeycreatecon(se_state.new_context)) { +- sudo_warn(U_("unable to set key creation context to %s"), se_state.new_context); +- if (se_state.enforcing) +- debug_return; +- } +-#endif /* HAVE_SETKEYCREATECON */ ++ /* Set SELinux exec and keycreate contexts. */ ++ if (selinux_setcon() == -1) ++ debug_return; + + /* + * Build new argv with sesh as argv[0]. +diff -up ./src/sudo.c.symbolic-link-attack-3 ./src/sudo.c +--- ./src/sudo.c.symbolic-link-attack-3 2021-02-02 17:12:32.773182386 +0100 ++++ ./src/sudo.c 2021-02-02 17:12:48.510964009 +0100 +@@ -971,10 +971,6 @@ run_command(struct command_details *deta + case CMD_WSTATUS: + /* Command ran, exited or was killed. */ + status = cstat.val; +-#ifdef HAVE_SELINUX +- if (ISSET(details->flags, CD_SUDOEDIT_COPY)) +- break; +-#endif + sudo_debug_printf(SUDO_DEBUG_DEBUG, + "calling policy close with wait status %d", status); + policy_close(&policy_plugin, status, 0); +diff -up ./src/sudo_edit.c.symbolic-link-attack-3 ./src/sudo_edit.c +--- ./src/sudo_edit.c.symbolic-link-attack-3 2021-02-02 17:11:32.380020435 +0100 ++++ ./src/sudo_edit.c 2021-02-02 17:11:32.382020407 +0100 +@@ -757,28 +757,54 @@ bad: + + #ifdef HAVE_SELINUX + static int ++selinux_run_helper(char *argv[], char *envp[]) ++{ ++ int status, ret = SESH_ERR_FAILURE; ++ const char *sesh; ++ pid_t child, pid; ++ debug_decl(selinux_run_helper, SUDO_DEBUG_EDIT); ++ ++ sesh = sudo_conf_sesh_path(); ++ if (sesh == NULL) { ++ sudo_warnx("internal error: sesh path not set"); ++ debug_return_int(-1); ++ } ++ ++ child = sudo_debug_fork(); ++ switch (child) { ++ case -1: ++ sudo_warn(U_("unable to fork")); ++ break; ++ case 0: ++ /* child runs sesh in new context */ ++ if (selinux_setcon() == 0) ++ execve(sesh, argv, envp); ++ _exit(SESH_ERR_FAILURE); ++ default: ++ /* parent waits */ ++ do { ++ pid = waitpid(child, &status, 0); ++ } while (pid == -1 && errno == EINTR); ++ ++ ret = WIFSIGNALED(status) ? SESH_ERR_KILLED : WEXITSTATUS(status); ++ } ++ ++ debug_return_int(ret); ++} ++ ++static int + selinux_edit_create_tfiles(struct command_details *command_details, + struct tempfile *tf, char *files[], int nfiles) + { + char **sesh_args, **sesh_ap; + int i, rc, sesh_nargs; + struct stat sb; +- struct command_details saved_command_details; + debug_decl(selinux_edit_create_tfiles, SUDO_DEBUG_EDIT) +- +- /* Prepare selinux stuff (setexeccon) */ +- if (selinux_setup(command_details->selinux_role, +- command_details->selinux_type, NULL, -1) != 0) +- debug_return_int(-1); + + if (nfiles < 1) + debug_return_int(0); + + /* Construct common args for sesh */ +- memcpy(&saved_command_details, command_details, sizeof(struct command_details)); +- command_details->command = _PATH_SUDO_SESH; +- command_details->flags |= CD_SUDOEDIT_COPY; +- + sesh_nargs = 4 + (nfiles * 2) + 1; + sesh_args = sesh_ap = reallocarray(NULL, sesh_nargs, sizeof(char *)); + if (sesh_args == NULL) { +@@ -791,6 +817,7 @@ selinux_edit_create_tfiles(struct comman + *sesh_ap++ = "-h"; + *sesh_ap++ = "0"; + ++ /* XXX - temp files should be created with user's context */ + for (i = 0; i < nfiles; i++) { + char *tfile, *ofile = files[i]; + int tfd; +@@ -820,8 +847,7 @@ selinux_edit_create_tfiles(struct comman + *sesh_ap = NULL; + + /* Run sesh -e [-h] 0 ... */ +- command_details->argv = sesh_args; +- rc = run_command(command_details); ++ rc = selinux_run_helper(sesh_args, command_details->envp); + switch (rc) { + case SESH_SUCCESS: + break; +@@ -829,15 +855,12 @@ selinux_edit_create_tfiles(struct comman + sudo_fatalx(U_("sesh: internal error: odd number of paths")); + case SESH_ERR_NO_FILES: + sudo_fatalx(U_("sesh: unable to create temporary files")); ++ case SESH_ERR_KILLED: ++ sudo_fatalx(U_("sesh: killed by a signal")); + default: + sudo_fatalx(U_("sesh: unknown error %d"), rc); + } + +- /* Restore saved command_details. */ +- command_details->command = saved_command_details.command; +- command_details->flags = saved_command_details.flags; +- command_details->argv = saved_command_details.argv; +- + /* Chown to user's UID so they can edit the temporary files. */ + for (i = 0; i < nfiles; i++) { + if (chown(tf[i].tfile, user_details.uid, user_details.gid) != 0) { +@@ -858,24 +881,14 @@ selinux_edit_copy_tfiles(struct command_ + { + char **sesh_args, **sesh_ap; + int i, rc, sesh_nargs, ret = 1; +- struct command_details saved_command_details; + struct timespec ts; + struct stat sb; + debug_decl(selinux_edit_copy_tfiles, SUDO_DEBUG_EDIT) +- +- /* Prepare selinux stuff (setexeccon) */ +- if (selinux_setup(command_details->selinux_role, +- command_details->selinux_type, NULL, -1) != 0) +- debug_return_int(1); + + if (nfiles < 1) + debug_return_int(0); + + /* Construct common args for sesh */ +- memcpy(&saved_command_details, command_details, sizeof(struct command_details)); +- command_details->command = _PATH_SUDO_SESH; +- command_details->flags |= CD_SUDOEDIT_COPY; +- + sesh_nargs = 3 + (nfiles * 2) + 1; + sesh_args = sesh_ap = reallocarray(NULL, sesh_nargs, sizeof(char *)); + if (sesh_args == NULL) { +@@ -913,32 +926,29 @@ selinux_edit_copy_tfiles(struct command_ + + if (sesh_ap - sesh_args > 3) { + /* Run sesh -e 1 ... */ +- command_details->argv = sesh_args; +- rc = run_command(command_details); ++ rc = selinux_run_helper(sesh_args, command_details->envp); + switch (rc) { + case SESH_SUCCESS: + ret = 0; + break; + case SESH_ERR_NO_FILES: + sudo_warnx(U_("unable to copy temporary files back to their original location")); +- sudo_warnx(U_("contents of edit session left in %s"), edit_tmpdir); + break; + case SESH_ERR_SOME_FILES: + sudo_warnx(U_("unable to copy some of the temporary files back to their original location")); +- sudo_warnx(U_("contents of edit session left in %s"), edit_tmpdir); ++ break; ++ case SESH_ERR_KILLED: ++ sudo_warnx(U_("sesh: killed by a signal")); + break; + default: + sudo_warnx(U_("sesh: unknown error %d"), rc); + break; + } ++ if (ret != 0) ++ sudo_warnx(U_("contents of edit session left in %s"), edit_tmpdir); + } + free(sesh_args); + +- /* Restore saved command_details. */ +- command_details->command = saved_command_details.command; +- command_details->flags = saved_command_details.flags; +- command_details->argv = saved_command_details.argv; +- + debug_return_int(ret); + } + #endif /* HAVE_SELINUX */ +@@ -990,6 +1000,15 @@ sudo_edit(struct command_details *comman + goto cleanup; + } + ++#ifdef HAVE_SELINUX ++ /* Compute new SELinux security context. */ ++ if (ISSET(command_details->flags, CD_RBAC_ENABLED)) { ++ if (selinux_setup(command_details->selinux_role, ++ command_details->selinux_type, NULL, -1, false) != 0) ++ goto cleanup; ++ } ++#endif ++ + /* Copy editor files to temporaries. */ + tf = calloc(nfiles, sizeof(*tf)); + if (tf == NULL) { +@@ -1025,6 +1044,7 @@ sudo_edit(struct command_details *comman + /* + * Run the editor with the invoking user's creds, + * keeping track of the time spent in the editor. ++ * XXX - should run editor with user's context + */ + if (sudo_gettime_real(×[0]) == -1) { + sudo_warn(U_("unable to read the clock")); +diff -up ./src/sudo_exec.h.symbolic-link-attack-3 ./src/sudo_exec.h +--- ./src/sudo_exec.h.symbolic-link-attack-3 2021-02-02 17:11:32.380020435 +0100 ++++ ./src/sudo_exec.h 2021-02-02 17:11:32.382020407 +0100 +@@ -73,6 +73,7 @@ + */ + #define SESH_SUCCESS 0 /* successful operation */ + #define SESH_ERR_FAILURE 1 /* unspecified error */ ++#define SESH_ERR_KILLED 2 /* killed by a signal */ + #define SESH_ERR_INVALID 30 /* invalid -e arg value */ + #define SESH_ERR_BAD_PATHS 31 /* odd number of paths */ + #define SESH_ERR_NO_FILES 32 /* copy error, no files copied */ +diff -up ./src/sudo.h.symbolic-link-attack-3 ./src/sudo.h +--- ./src/sudo.h.symbolic-link-attack-3 2019-10-28 13:28:52.000000000 +0100 ++++ ./src/sudo.h 2021-02-02 17:11:32.382020407 +0100 +@@ -135,12 +135,11 @@ struct user_details { + #define CD_USE_PTY 0x001000 + #define CD_SET_UTMP 0x002000 + #define CD_EXEC_BG 0x004000 +-#define CD_SUDOEDIT_COPY 0x008000 +-#define CD_SUDOEDIT_FOLLOW 0x010000 +-#define CD_SUDOEDIT_CHECKDIR 0x020000 +-#define CD_SET_GROUPS 0x040000 +-#define CD_LOGIN_SHELL 0x080000 +-#define CD_OVERRIDE_UMASK 0x100000 ++#define CD_SUDOEDIT_FOLLOW 0x008000 ++#define CD_SUDOEDIT_CHECKDIR 0x010000 ++#define CD_SET_GROUPS 0x020000 ++#define CD_LOGIN_SHELL 0x040000 ++#define CD_OVERRIDE_UMASK 0x080000 + + struct preserved_fd { + TAILQ_ENTRY(preserved_fd) entries; +@@ -240,7 +239,8 @@ int os_init_openbsd(int argc, char *argv + /* selinux.c */ + int selinux_restore_tty(void); + int selinux_setup(const char *role, const char *type, const char *ttyn, +- int ttyfd); ++ int ttyfd, bool label_tty); ++int selinux_setcon(void); + void selinux_execve(int fd, const char *path, char *const argv[], + char *envp[], bool noexec); + diff --git a/SOURCES/sudo-1.9.5-CVE-2021-23240-4.patch b/SOURCES/sudo-1.9.5-CVE-2021-23240-4.patch new file mode 100644 index 0000000..b6d2813 --- /dev/null +++ b/SOURCES/sudo-1.9.5-CVE-2021-23240-4.patch @@ -0,0 +1,380 @@ +diff -up ./src/copy_file.c.symbolic-link-attack-4 ./src/copy_file.c +--- ./src/copy_file.c.symbolic-link-attack-4 2021-02-02 16:35:18.453036846 +0100 ++++ ./src/copy_file.c 2021-02-02 16:38:09.430731749 +0100 +@@ -23,6 +23,7 @@ + + #include + ++#include + #include + + #include +@@ -126,3 +127,35 @@ write_error: + debug_return_int(-1); + } + } ++ ++#ifdef HAVE_SELINUX ++bool ++sudo_check_temp_file(int tfd, const char *tfile, uid_t uid, struct stat *sb) ++{ ++ struct stat sbuf; ++ debug_decl(sudo_check_temp_file, SUDO_DEBUG_UTIL); ++ ++ if (sb == NULL) ++ sb = &sbuf; ++ ++ if (fstat(tfd, sb) == -1) { ++ sudo_warn(U_("unable to stat %s"), tfile); ++ debug_return_bool(false); ++ } ++ if (!S_ISREG(sb->st_mode)) { ++ sudo_warnx(U_("%s: not a regular file"), tfile); ++ debug_return_bool(false); ++ } ++ if ((sb->st_mode & ALLPERMS) != (S_IRUSR|S_IWUSR)) { ++ sudo_warnx(U_("%s: bad file mode: 0%o"), tfile, ++ (unsigned int)(sb->st_mode & ALLPERMS)); ++ debug_return_bool(false); ++ } ++ if (sb->st_uid != uid) { ++ sudo_warnx(U_("%s is owned by uid %u, should be %u"), ++ tfile, (unsigned int)sb->st_uid, (unsigned int)uid); ++ debug_return_bool(false); ++ } ++ debug_return_bool(true); ++} ++#endif /* SELINUX */ +diff -up ./src/sesh.c.symbolic-link-attack-4 ./src/sesh.c +--- ./src/sesh.c.symbolic-link-attack-4 2021-02-02 16:35:18.450036887 +0100 ++++ ./src/sesh.c 2021-02-02 16:38:52.907146897 +0100 +@@ -134,7 +134,7 @@ main(int argc, char *argv[], char *envp[ + static int + sesh_sudoedit(int argc, char *argv[]) + { +- int i, oflags_dst, post, ret = SESH_ERR_FAILURE; ++ int i, oflags_src, oflags_dst, post, ret = SESH_ERR_FAILURE; + int fd_src = -1, fd_dst = -1, follow = 0; + ssize_t nread, nwritten; + struct stat sb; +@@ -178,10 +178,12 @@ sesh_sudoedit(int argc, char *argv[]) + debug_return_int(SESH_ERR_BAD_PATHS); + + /* +- * Use O_EXCL if we are not in the post editing stage +- * so that it's ensured that the temporary files are +- * created by us and that we are not opening any symlinks. ++ * In the pre-editing stage, use O_EXCL to ensure that the temporary ++ * files are created by us and that we are not opening any symlinks. ++ * In the post-editing stage, use O_NOFOLLOW so we don't follow symlinks ++ * when opening the temporary files. + */ ++ oflags_src = O_RDONLY|(post ? O_NONBLOCK|O_NOFOLLOW : follow); + oflags_dst = O_WRONLY|O_CREAT|(post ? follow : O_EXCL); + for (i = 0; i < argc - 1; i += 2) { + const char *path_src = argv[i]; +@@ -191,7 +193,7 @@ sesh_sudoedit(int argc, char *argv[]) + * doesn't exist, that's OK, we'll create an empty + * destination file. + */ +- if ((fd_src = open(path_src, O_RDONLY|follow, S_IRUSR|S_IWUSR)) < 0) { ++ if ((fd_src = open(path_src, oflags_src, S_IRUSR|S_IWUSR)) < 0) { + if (errno != ENOENT) { + sudo_warn("%s", path_src); + if (post) { +@@ -201,6 +203,14 @@ sesh_sudoedit(int argc, char *argv[]) + goto cleanup_0; + } + } ++ if (post) { ++ /* Make sure the temporary file is safe and has the proper owner. */ ++ if (!sudo_check_temp_file(fd_src, path_src, geteuid(), &sb)) { ++ ret = SESH_ERR_SOME_FILES; ++ goto nocleanup; ++ } ++ fcntl(fd_src, F_SETFL, fcntl(fd_src, F_GETFL, 0) & ~O_NONBLOCK); ++ } + + if ((fd_dst = open(path_dst, oflags_dst, post ? + (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) : (S_IRUSR|S_IWUSR))) < 0) { +@@ -218,10 +228,7 @@ sesh_sudoedit(int argc, char *argv[]) + off_t len_dst = -1; + + if (post) { +- if (fstat(fd_src, &sb) != 0) { +- ret = SESH_ERR_SOME_FILES; +- goto nocleanup; +- } ++ /* sudo_check_temp_file() filled in sb for us. */ + len_src = sb.st_size; + if (fstat(fd_dst, &sb) != 0) { + ret = SESH_ERR_SOME_FILES; +diff -up ./src/sudo_edit.c.symbolic-link-attack-4 ./src/sudo_edit.c +--- ./src/sudo_edit.c.symbolic-link-attack-4 2021-02-02 16:35:18.452036860 +0100 ++++ ./src/sudo_edit.c 2021-02-02 16:54:25.943429580 +0100 +@@ -253,8 +253,10 @@ sudo_edit_mktemp(const char *ofile, char + } else { + len = asprintf(tfile, "%s/%s.XXXXXXXX", edit_tmpdir, cp); + } +- if (len == -1) +- sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); ++ if (len == -1) { ++ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); ++ debug_return_int(-1); ++ } + tfd = mkstemps(*tfile, suff ? strlen(suff) : 0); + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "%s -> %s, fd %d", ofile, *tfile, tfd); +@@ -757,7 +759,8 @@ bad: + + #ifdef HAVE_SELINUX + static int +-selinux_run_helper(char *argv[], char *envp[]) ++selinux_run_helper(uid_t uid, gid_t gid, int ngroups, GETGROUPS_T *groups, ++ char *const argv[], char *const envp[]) + { + int status, ret = SESH_ERR_FAILURE; + const char *sesh; +@@ -777,8 +780,10 @@ selinux_run_helper(char *argv[], char *e + break; + case 0: + /* child runs sesh in new context */ +- if (selinux_setcon() == 0) ++ if (selinux_setcon() == 0) { ++ switch_user(uid, gid, ngroups, groups); + execve(sesh, argv, envp); ++ } + _exit(SESH_ERR_FAILURE); + default: + /* parent waits */ +@@ -797,7 +802,7 @@ selinux_edit_create_tfiles(struct comman + struct tempfile *tf, char *files[], int nfiles) + { + char **sesh_args, **sesh_ap; +- int i, rc, sesh_nargs; ++ int i, rc, error, sesh_nargs, ret = -1; + struct stat sb; + debug_decl(selinux_edit_create_tfiles, SUDO_DEBUG_EDIT) + +@@ -809,7 +814,7 @@ selinux_edit_create_tfiles(struct comman + sesh_args = sesh_ap = reallocarray(NULL, sesh_nargs, sizeof(char *)); + if (sesh_args == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); +- debug_return_int(-1); ++ goto done; + } + *sesh_ap++ = "sesh"; + *sesh_ap++ = "-e"; +@@ -817,7 +822,6 @@ selinux_edit_create_tfiles(struct comman + *sesh_ap++ = "-h"; + *sesh_ap++ = "0"; + +- /* XXX - temp files should be created with user's context */ + for (i = 0; i < nfiles; i++) { + char *tfile, *ofile = files[i]; + int tfd; +@@ -835,8 +839,7 @@ selinux_edit_create_tfiles(struct comman + if (tfd == -1) { + sudo_warn("mkstemps"); + free(tfile); +- free(sesh_args); +- debug_return_int(-1); ++ goto done; + } + /* Helper will re-create temp file with proper security context. */ + close(tfd); +@@ -847,8 +850,10 @@ selinux_edit_create_tfiles(struct comman + *sesh_ap = NULL; + + /* Run sesh -e [-h] 0 ... */ +- rc = selinux_run_helper(sesh_args, command_details->envp); +- switch (rc) { ++ error = selinux_run_helper(command_details->uid, command_details->gid, ++ command_details->ngroups, command_details->groups, sesh_args, ++ command_details->envp); ++ switch (error) { + case SESH_SUCCESS: + break; + case SESH_ERR_BAD_PATHS: +@@ -858,21 +863,34 @@ selinux_edit_create_tfiles(struct comman + case SESH_ERR_KILLED: + sudo_fatalx(U_("sesh: killed by a signal")); + default: +- sudo_fatalx(U_("sesh: unknown error %d"), rc); ++ sudo_fatalx(U_("sesh: unknown error %d"), error); ++ goto done; + } + +- /* Chown to user's UID so they can edit the temporary files. */ + for (i = 0; i < nfiles; i++) { +- if (chown(tf[i].tfile, user_details.uid, user_details.gid) != 0) { +- sudo_warn("unable to chown(%s) to %d:%d for editing", +- tf[i].tfile, user_details.uid, user_details.gid); +- } ++ int tfd = open(tf[i].tfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW); ++ if (tfd == -1) { ++ sudo_warn(U_("unable to open %s"), tf[i].tfile); ++ goto done; ++ } ++ if (!sudo_check_temp_file(tfd, tf[i].tfile, command_details->uid, NULL)) { ++ close(tfd); ++ goto done; ++ } ++ if (fchown(tfd, user_details.uid, user_details.gid) != 0) { ++ sudo_warn("unable to chown(%s) to %d:%d for editing", ++ tf[i].tfile, user_details.uid, user_details.gid); ++ close(tfd); ++ goto done; ++ } ++ close(tfd); + } + ++done: + /* Contents of tf will be freed by caller. */ + free(sesh_args); + +- return (nfiles); ++ debug_return_int(ret); + } + + static int +@@ -880,7 +898,8 @@ selinux_edit_copy_tfiles(struct command_ + struct tempfile *tf, int nfiles, struct timespec *times) + { + char **sesh_args, **sesh_ap; +- int i, rc, sesh_nargs, ret = 1; ++ int i, rc, error, sesh_nargs, ret = 1; ++ int tfd = -1; + struct timespec ts; + struct stat sb; + debug_decl(selinux_edit_copy_tfiles, SUDO_DEBUG_EDIT) +@@ -901,33 +920,43 @@ selinux_edit_copy_tfiles(struct command_ + + /* Construct args for sesh -e 1 */ + for (i = 0; i < nfiles; i++) { +- if (stat(tf[i].tfile, &sb) == 0) { +- mtim_get(&sb, ts); +- if (tf[i].osize == sb.st_size && sudo_timespeccmp(&tf[i].omtim, &ts, ==)) { +- /* +- * If mtime and size match but the user spent no measurable +- * time in the editor we can't tell if the file was changed. +- */ +- if (sudo_timespeccmp(×[0], ×[1], !=)) { +- sudo_warnx(U_("%s unchanged"), tf[i].ofile); +- unlink(tf[i].tfile); +- continue; +- } ++ if (tfd != -1) ++ close(tfd); ++ if ((tfd = open(tf[i].tfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW)) == -1) { ++ sudo_warn(U_("unable to open %s"), tf[i].tfile); ++ continue; ++ } ++ if (!sudo_check_temp_file(tfd, tf[i].tfile, user_details.uid, &sb)) ++ continue; ++ mtim_get(&sb, ts); ++ if (tf[i].osize == sb.st_size && sudo_timespeccmp(&tf[i].omtim, &ts, ==)) { ++ /* ++ * If mtime and size match but the user spent no measurable ++ * time in the editor we can't tell if the file was changed. ++ */ ++ if (sudo_timespeccmp(×[0], ×[1], !=)) { ++ sudo_warnx(U_("%s unchanged"), tf[i].ofile); ++ unlink(tf[i].tfile); ++ continue; + } + } + *sesh_ap++ = tf[i].tfile; + *sesh_ap++ = tf[i].ofile; +- if (chown(tf[i].tfile, command_details->uid, command_details->gid) != 0) { ++ if (fchown(tfd, command_details->uid, command_details->gid) != 0) { + sudo_warn("unable to chown(%s) back to %d:%d", tf[i].tfile, + command_details->uid, command_details->gid); + } + } + *sesh_ap = NULL; ++ if (tfd != -1) ++ close(tfd); + + if (sesh_ap - sesh_args > 3) { + /* Run sesh -e 1 ... */ +- rc = selinux_run_helper(sesh_args, command_details->envp); +- switch (rc) { ++ error = selinux_run_helper(command_details->uid, command_details->gid, ++ command_details->ngroups, command_details->groups, sesh_args, ++ command_details->envp); ++ switch (error) { + case SESH_SUCCESS: + ret = 0; + break; +@@ -941,7 +970,7 @@ selinux_edit_copy_tfiles(struct command_ + sudo_warnx(U_("sesh: killed by a signal")); + break; + default: +- sudo_warnx(U_("sesh: unknown error %d"), rc); ++ sudo_warnx(U_("sesh: unknown error %d"), error); + break; + } + if (ret != 0) +@@ -963,7 +992,7 @@ sudo_edit(struct command_details *comman + { + struct command_details saved_command_details; + char **nargv = NULL, **ap, **files = NULL; +- int errors, i, ac, nargc, rc; ++ int errors, i, ac, nargc, ret; + int editor_argc = 0, nfiles = 0; + struct timespec times[2]; + struct tempfile *tf = NULL; +@@ -1058,7 +1087,7 @@ sudo_edit(struct command_details *comman + command_details->ngroups = user_details.ngroups; + command_details->groups = user_details.groups; + command_details->argv = nargv; +- rc = run_command(command_details); ++ ret = run_command(command_details); + if (sudo_gettime_real(×[1]) == -1) { + sudo_warn(U_("unable to read the clock")); + goto cleanup; +@@ -1080,14 +1109,16 @@ sudo_edit(struct command_details *comman + else + #endif + errors = sudo_edit_copy_tfiles(command_details, tf, nfiles, times); +- if (errors) +- goto cleanup; ++ if (errors) { ++ /* Preserve the edited temporary files. */ ++ ret = W_EXITCODE(1, 0); ++ } + + for (i = 0; i < nfiles; i++) + free(tf[i].tfile); + free(tf); + free(nargv); +- debug_return_int(rc); ++ debug_return_int(ret); + + cleanup: + /* Clean up temp files and return. */ +diff -up ./src/sudo_exec.h.symbolic-link-attack-4 ./src/sudo_exec.h +--- ./src/sudo_exec.h.symbolic-link-attack-4 2021-02-02 16:35:18.452036860 +0100 ++++ ./src/sudo_exec.h 2021-02-02 16:35:18.454036833 +0100 +@@ -1,7 +1,7 @@ + /* + * SPDX-License-Identifier: ISC + * +- * Copyright (c) 2010-2016 Todd C. Miller ++ * Copyright (c) 2010-2017, 2020-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 +@@ -84,9 +84,11 @@ + */ + struct command_details; + struct command_status; ++struct stat; + + /* copy_file.c */ + int sudo_copy_file(const char *src, int src_fd, off_t src_len, const char *dst, int dst_fd, off_t dst_len); ++bool sudo_check_temp_file(int tfd, const char *tname, uid_t uid, struct stat *sb); + + /* exec.c */ + void exec_cmnd(struct command_details *details, int errfd); diff --git a/SOURCES/sudo-1.9.5-CVE-2021-23240-5.patch b/SOURCES/sudo-1.9.5-CVE-2021-23240-5.patch new file mode 100644 index 0000000..fd1bbd1 --- /dev/null +++ b/SOURCES/sudo-1.9.5-CVE-2021-23240-5.patch @@ -0,0 +1,47 @@ +diff -up ./src/copy_file.c.symbolic-link-attack-5 ./src/copy_file.c +--- ./src/copy_file.c.symbolic-link-attack-5 2021-02-02 17:18:05.355567274 +0100 ++++ ./src/copy_file.c 2021-02-02 17:19:09.904671563 +0100 +@@ -128,7 +128,6 @@ write_error: + } + } + +-#ifdef HAVE_SELINUX + bool + sudo_check_temp_file(int tfd, const char *tfile, uid_t uid, struct stat *sb) + { +@@ -158,4 +157,3 @@ sudo_check_temp_file(int tfd, const char + } + debug_return_bool(true); + } +-#endif /* SELINUX */ +diff -up ./src/sudo_edit.c.symbolic-link-attack-5 ./src/sudo_edit.c +--- ./src/sudo_edit.c.symbolic-link-attack-5 2021-02-02 17:18:05.355567274 +0100 ++++ ./src/sudo_edit.c 2021-02-02 17:18:05.356567260 +0100 +@@ -692,24 +692,17 @@ sudo_edit_copy_tfiles(struct command_det + + /* Copy contents of temp files to real ones. */ + for (i = 0; i < nfiles; i++) { +- int rc = -1; + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "seteuid(%u)", (unsigned int)user_details.uid); + if (seteuid(user_details.uid) != 0) + sudo_fatal("seteuid(%u)", (unsigned int)user_details.uid); + tfd = sudo_edit_open(tf[i].tfile, O_RDONLY, + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, NULL); +- if (tfd != -1) +- rc = fstat(tfd, &sb); +- sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, +- "seteuid(%u)", ROOT_UID); + if (seteuid(ROOT_UID) != 0) + sudo_fatal("seteuid(ROOT_UID)"); +- if (rc == -1 || !S_ISREG(sb.st_mode)) { +- if (rc == -1) +- sudo_warn("%s", tf[i].tfile); +- else +- sudo_warnx(U_("%s: not a regular file"), tf[i].tfile); ++ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, ++ "seteuid(%u)", ROOT_UID); ++ if (tfd == -1 || !sudo_check_temp_file(tfd, tf[i].tfile, user_details.uid, &sb)) { + sudo_warnx(U_("%s left unmodified"), tf[i].ofile); + if (tfd != -1) + close(tfd); diff --git a/SPECS/sudo.spec b/SPECS/sudo.spec index 949aab9..6b5bddc 100644 --- a/SPECS/sudo.spec +++ b/SPECS/sudo.spec @@ -1,10 +1,10 @@ Summary: Allows restricted root access for specified users Name: sudo Version: 1.8.29 -Release: 6%{?dist}.1 +Release: 7%{?dist} License: ISC Group: Applications/System -URL: http://www.courtesan.com/sudo/ +URL: https://www.sudo.ws/ Source0: https://www.sudo.ws/dist/%{name}-%{version}.tar.gz Source1: sudoers @@ -56,8 +56,16 @@ Patch9: sudo-1.8.29-CVE-2019-18634.patch Patch10: sudo-1.8.29-expired-password-part1.patch Patch11: sudo-1.8.29-expired-password-part2.patch -# 1917732 - EMBARGOED CVE-2021-3156 sudo: Heap-buffer overflow in argument parsing [rhel-8.3.0.z] +# 1917734 - EMBARGOED CVE-2021-3156 sudo: Heap-buffer overflow in argument parsing [rhel-8.4.0] Patch12: sudo-1.8.31-CVE-2021-3156.patch +# 1916434 - CVE-2021-23239 sudo: possible directory existence test due to race condition in sudoedit [rhel-8] +Patch13: sudo-1.9.5-CVE-2021-23239.patch +# 1917038 - CVE-2021-23240 sudo: symbolic link attack in SELinux-enabled sudoedit [rhel-8] +Patch14: sudo-1.9.5-CVE-2021-23240-1.patch +Patch15: sudo-1.9.5-CVE-2021-23240-2.patch +Patch16: sudo-1.9.5-CVE-2021-23240-3.patch +Patch17: sudo-1.9.5-CVE-2021-23240-4.patch +Patch18: sudo-1.9.5-CVE-2021-23240-5.patch %description Sudo (superuser do) allows a system administrator to give certain @@ -97,6 +105,14 @@ plugins that use %{name}. %patch12 -p1 -b .heap-buffer +%patch13 -p1 -b .sudoedit-race + +%patch14 -p1 -b .symbolic-link-attack-1 +%patch15 -p1 -b .symbolic-link-attack-2 +%patch16 -p1 -b .symbolic-link-attack-3 +%patch17 -p1 -b .symbolic-link-attack-4 +%patch18 -p1 -b .symbolic-link-attack-5 + %build # Remove bundled copy of zlib rm -rf zlib/ @@ -255,10 +271,16 @@ rm -rf $RPM_BUILD_ROOT %{_mandir}/man8/sudo_plugin.8* %changelog -* Wed Jan 20 2021 Radovan Sroka - 1.8.29-6.1 -- RHEL 8.3.Z ERRATUM +* Tue Feb 02 2021 Radovan Sroka - 1.8.29-7 +- RHEL 8.4 ERRATUM - CVE-2021-3156 -Resolves: rhbz#1917732 +Resolves: rhbz#1917734 +- CVE-2021-23239 sudo: possible directory existence test due to race condition in sudoedit +Resolves: rhzb#1916434 +- CVE-2021-23240 sudo: symbolic link attack in SELinux-enabled sudoedit +Resolves: rhbz#1917038 +- updated upstream url +Resolves: rhbz#1923825 * Tue Apr 28 2020 Radovan Sroka - 1.8.29-6 - RHEL 8.3 ERRATUM