import sudo-1.8.29-7.el8

This commit is contained in:
CentOS Sources 2021-05-18 02:46:33 -04:00 committed by Andrew Lukoshko
parent 777d690e00
commit d13292b8f6
7 changed files with 1450 additions and 6 deletions

View File

@ -0,0 +1,61 @@
From db1f27c0350e9e437c93780ffe88648ae1984467 Mon Sep 17 00:00:00 2001
From: "Todd C. Miller" <Todd.Miller@sudo.ws>
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

View File

@ -0,0 +1,158 @@
From adb4360c40df99238c17c3ecedcb1d32d76e2b2e Mon Sep 17 00:00:00 2001
From: "Todd C. Miller" <Todd.Miller@sudo.ws>
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 <Todd.Miller@sudo.ws>
+ * Copyright (c) 2004-2008, 2010-2020 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
@@ -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

View File

@ -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 <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/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#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 <Todd.Miller@sudo.ws>
+ * Copyright (c) 2008, 2010-2018, 2020 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
@@ -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 <grp.h>
#include <pwd.h>
#include <signal.h>
-#include <errno.h>
#include <fcntl.h>
#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);

View File

@ -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 <o1> <t1> ... <on> <tn> */
- 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 <t1> <o1> ... <tn> <on> */
- 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(&times[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);

View File

@ -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 <config.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
@@ -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 <o1> <t1> ... <on> <tn> */
- 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(&times[0], &times[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(&times[0], &times[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 <t1> <o1> ... <tn> <on> */
- 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(&times[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 <Todd.Miller@sudo.ws>
+ * Copyright (c) 2010-2017, 2020-2021 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
@@ -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);

View File

@ -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);

View File

@ -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 <rsroka@redhat.com> - 1.8.29-6.1
- RHEL 8.3.Z ERRATUM
* Tue Feb 02 2021 Radovan Sroka <rsroka@redhat.com> - 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 <rsroka@redhat.com> - 1.8.29-6
- RHEL 8.3 ERRATUM