9f5db7
diff -up ./src/copy_file.c.symbolic-link-attack-2 ./src/copy_file.c
9f5db7
--- ./src/copy_file.c.symbolic-link-attack-2	2021-02-02 15:31:20.555340446 +0100
9f5db7
+++ ./src/copy_file.c	2021-02-02 15:31:20.555340446 +0100
9f5db7
@@ -0,0 +1,128 @@
9f5db7
+/*
9f5db7
+ * SPDX-License-Identifier: ISC
9f5db7
+ *
9f5db7
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
9f5db7
+ *
9f5db7
+ * Permission to use, copy, modify, and distribute this software for any
9f5db7
+ * purpose with or without fee is hereby granted, provided that the above
9f5db7
+ * copyright notice and this permission notice appear in all copies.
9f5db7
+ *
9f5db7
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9f5db7
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9f5db7
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
9f5db7
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
9f5db7
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
9f5db7
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
9f5db7
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
9f5db7
+ */
9f5db7
+
9f5db7
+/*
9f5db7
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
9f5db7
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
9f5db7
+ */
9f5db7
+
9f5db7
+#include <config.h>
9f5db7
+
9f5db7
+#include <sys/types.h>
9f5db7
+
9f5db7
+#include <stdio.h>
9f5db7
+#include <stdlib.h>
9f5db7
+#include <unistd.h>
9f5db7
+#include <errno.h>
9f5db7
+
9f5db7
+#include "sudo.h"
9f5db7
+
9f5db7
+/*
9f5db7
+ * Extend the given fd to the specified size in bytes.
9f5db7
+ * We do this to allocate disk space up-front before overwriting
9f5db7
+ * the original file with the temporary.  Otherwise, we could
9f5db7
+ * we run out of disk space after truncating the original file.
9f5db7
+ */
9f5db7
+static int
9f5db7
+sudo_extend_file(int fd, const char *name, off_t new_size)
9f5db7
+{
9f5db7
+    off_t old_size, size;
9f5db7
+    ssize_t nwritten;
9f5db7
+    char zeroes[BUFSIZ] = { '\0' };
9f5db7
+    debug_decl(sudo_extend_file, SUDO_DEBUG_UTIL);
9f5db7
+
9f5db7
+    if ((old_size = lseek(fd, 0, SEEK_END)) == -1) {
9f5db7
+	sudo_warn("lseek");
9f5db7
+	debug_return_int(-1);
9f5db7
+    }
9f5db7
+    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: extending %s from %lld to %lld",
9f5db7
+	__func__, name, (long long)old_size, (long long)new_size);
9f5db7
+
9f5db7
+    for (size = old_size; size < new_size; size += nwritten) {
9f5db7
+	size_t len = new_size - size;
9f5db7
+	if (len > sizeof(zeroes))
9f5db7
+	    len = sizeof(zeroes);
9f5db7
+	nwritten = write(fd, zeroes, len);
9f5db7
+	if (nwritten == -1) {
9f5db7
+	    int serrno = errno;
9f5db7
+	    if (ftruncate(fd, old_size) == -1) {
9f5db7
+		sudo_debug_printf(
9f5db7
+		    SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
9f5db7
+		    "unable to truncate %s to %lld", name, (long long)old_size);
9f5db7
+	    }
9f5db7
+	    errno = serrno;
9f5db7
+	    debug_return_int(-1);
9f5db7
+	}
9f5db7
+    }
9f5db7
+    if (lseek(fd, 0, SEEK_SET) == -1) {
9f5db7
+	sudo_warn("lseek");
9f5db7
+	debug_return_int(-1);
9f5db7
+    }
9f5db7
+
9f5db7
+    debug_return_int(0);
9f5db7
+}
9f5db7
+
9f5db7
+/*
9f5db7
+ * Copy the contents of src_fd into dst_fd.
9f5db7
+ * Returns 0 on success or -1 on error.
9f5db7
+ */
9f5db7
+int
9f5db7
+sudo_copy_file(const char *src, int src_fd, off_t src_len, const char *dst,
9f5db7
+    int dst_fd, off_t dst_len)
9f5db7
+{
9f5db7
+    char buf[BUFSIZ];
9f5db7
+    ssize_t nwritten, nread;
9f5db7
+    debug_decl(sudo_copy_file, SUDO_DEBUG_UTIL);
9f5db7
+
9f5db7
+    /* Extend the file to the new size if larger before copying. */
9f5db7
+    if (dst_len > 0 && src_len > dst_len) {
9f5db7
+	if (sudo_extend_file(dst_fd, dst, src_len) == -1)
9f5db7
+	    goto write_error;
9f5db7
+    }
9f5db7
+
9f5db7
+    /* Overwrite the old file with the new contents. */
9f5db7
+    while ((nread = read(src_fd, buf, sizeof(buf))) > 0) {
9f5db7
+	ssize_t off = 0;
9f5db7
+	do {
9f5db7
+	    nwritten = write(dst_fd, buf + off, nread - off);
9f5db7
+	    if (nwritten == -1)
9f5db7
+		goto write_error;
9f5db7
+	    off += nwritten;
9f5db7
+	} while (nread > off);
9f5db7
+    }
9f5db7
+    if (nread == 0) {
9f5db7
+	/* success, read to EOF */
9f5db7
+	if (src_len < dst_len) {
9f5db7
+	    /* We don't open with O_TRUNC so must truncate manually. */
9f5db7
+	    if (ftruncate(dst_fd, src_len) == -1) {
9f5db7
+		sudo_debug_printf(
9f5db7
+		    SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
9f5db7
+		    "unable to truncate %s to %lld", dst, (long long)src_len);
9f5db7
+		goto write_error;
9f5db7
+	    }
9f5db7
+	}
9f5db7
+	debug_return_int(0);
9f5db7
+    } else if (nread < 0) {
9f5db7
+	sudo_warn(U_("unable to read from %s"), src);
9f5db7
+	debug_return_int(-1);
9f5db7
+    } else {
9f5db7
+write_error:
9f5db7
+	sudo_warn(U_("unable to write to %s"), dst);
9f5db7
+	debug_return_int(-1);
9f5db7
+    }
9f5db7
+}
9f5db7
diff -up ./src/Makefile.in.symbolic-link-attack-2 ./src/Makefile.in
9f5db7
--- ./src/Makefile.in.symbolic-link-attack-2	2019-10-28 13:28:54.000000000 +0100
9f5db7
+++ ./src/Makefile.in	2021-02-02 15:31:20.555340446 +0100
9f5db7
@@ -120,16 +120,17 @@ SHELL = @SHELL@
9f5db7
 
9f5db7
 PROGS = @PROGS@
9f5db7
 
9f5db7
-OBJS = conversation.o env_hooks.o exec.o exec_common.o exec_monitor.o \
9f5db7
-       exec_nopty.o exec_pty.o get_pty.o hooks.o limits.o load_plugins.o \
9f5db7
-       net_ifs.o parse_args.o preserve_fds.o signal.o sudo.o sudo_edit.o \
9f5db7
-       tcsetpgrp_nobg.o tgetpass.o ttyname.o utmp.o @SUDO_OBJS@
9f5db7
+OBJS = conversation.o copy_file.o env_hooks.o exec.o exec_common.o \
9f5db7
+       exec_monitor.o exec_nopty.o exec_pty.o get_pty.o hooks.o \
9f5db7
+       limits.o load_plugins.o net_ifs.o parse_args.o preserve_fds.o \
9f5db7
+       signal.o sudo.o sudo_edit.o tcsetpgrp_nobg.o tgetpass.o \
9f5db7
+       ttyname.o utmp.o @SUDO_OBJS@
9f5db7
 
9f5db7
 IOBJS = $(OBJS:.o=.i) sesh.i
9f5db7
 
9f5db7
 POBJS = $(IOBJS:.i=.plog)
9f5db7
 
9f5db7
-SESH_OBJS = sesh.o exec_common.o
9f5db7
+SESH_OBJS = copy_file.o exec_common.o sesh.o
9f5db7
 
9f5db7
 CHECK_NOEXEC_OBJS = check_noexec.o exec_common.o
9f5db7
 
9f5db7
@@ -335,6 +336,22 @@ conversation.i: $(srcdir)/conversation.c
9f5db7
 	$(CC) -E -o $@ $(CPPFLAGS) $<
9f5db7
 conversation.plog: conversation.i
9f5db7
 	rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/conversation.c --i-file $< --output-file $@
9f5db7
+copy_file.o: $(srcdir)/copy_file.c $(incdir)/compat/stdbool.h \
9f5db7
+             $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
9f5db7
+             $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
9f5db7
+             $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
9f5db7
+             $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo.h \
9f5db7
+             $(top_builddir)/config.h $(top_builddir)/pathnames.h
9f5db7
+	$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/copy_file.c
9f5db7
+copy_file.i: $(srcdir)/copy_file.c $(incdir)/compat/stdbool.h \
9f5db7
+             $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
9f5db7
+             $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
9f5db7
+             $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
9f5db7
+             $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo.h \
9f5db7
+             $(top_builddir)/config.h $(top_builddir)/pathnames.h
9f5db7
+	$(CC) -E -o $@ $(CPPFLAGS) $<
9f5db7
+copy_file.plog: copy_file.i
9f5db7
+	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 $@
9f5db7
 env_hooks.o: $(srcdir)/env_hooks.c $(incdir)/compat/stdbool.h \
9f5db7
              $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
9f5db7
              $(incdir)/sudo_debug.h $(incdir)/sudo_dso.h \
9f5db7
diff -up ./src/sesh.c.symbolic-link-attack-2 ./src/sesh.c
9f5db7
--- ./src/sesh.c.symbolic-link-attack-2	2019-10-28 13:28:52.000000000 +0100
9f5db7
+++ ./src/sesh.c	2021-02-02 15:31:20.555340446 +0100
9f5db7
@@ -1,7 +1,7 @@
9f5db7
 /*
9f5db7
  * SPDX-License-Identifier: ISC
9f5db7
  *
9f5db7
- * Copyright (c) 2008, 2010-2018 Todd C. Miller <Todd.Miller@sudo.ws>
9f5db7
+ * Copyright (c) 2008, 2010-2018, 2020 Todd C. Miller <Todd.Miller@sudo.ws>
9f5db7
  *
9f5db7
  * Permission to use, copy, modify, and distribute this software for any
9f5db7
  * purpose with or without fee is hereby granted, provided that the above
9f5db7
@@ -182,7 +182,7 @@ sesh_sudoedit(int argc, char *argv[])
9f5db7
      * so that it's ensured that the temporary files are
9f5db7
      * created by us and that we are not opening any symlinks.
9f5db7
      */
9f5db7
-    oflags_dst = O_WRONLY|O_TRUNC|O_CREAT|(post ? follow : O_EXCL);
9f5db7
+    oflags_dst = O_WRONLY|O_CREAT|(post ? follow : O_EXCL);
9f5db7
     for (i = 0; i < argc - 1; i += 2) {
9f5db7
 	const char *path_src = argv[i];
9f5db7
 	const char *path_dst = argv[i + 1];
9f5db7
@@ -214,14 +214,29 @@ sesh_sudoedit(int argc, char *argv[])
9f5db7
 	}
9f5db7
 
9f5db7
 	if (fd_src != -1) {
9f5db7
-	    while ((nread = read(fd_src, buf, sizeof(buf))) > 0) {
9f5db7
-		if ((nwritten = write(fd_dst, buf, nread)) != nread) {
9f5db7
-		    sudo_warn("%s", path_src);
9f5db7
-		    if (post) {
9f5db7
-			ret = SESH_ERR_SOME_FILES;
9f5db7
-			goto nocleanup;
9f5db7
-		    } else
9f5db7
-			goto cleanup_0;
9f5db7
+	    off_t len_src = -1;
9f5db7
+	    off_t len_dst = -1;
9f5db7
+
9f5db7
+	    if (post) {
9f5db7
+		if (fstat(fd_src, &sb) != 0) {
9f5db7
+		    ret = SESH_ERR_SOME_FILES;
9f5db7
+		    goto nocleanup;
9f5db7
+		}
9f5db7
+		len_src = sb.st_size;
9f5db7
+		if (fstat(fd_dst, &sb) != 0) {
9f5db7
+		    ret = SESH_ERR_SOME_FILES;
9f5db7
+		    goto nocleanup;
9f5db7
+		}
9f5db7
+		len_dst = sb.st_size;
9f5db7
+	    }
9f5db7
+
9f5db7
+	    if (sudo_copy_file(path_src, fd_src, len_src, path_dst, fd_dst,
9f5db7
+		    len_dst) == -1) {
9f5db7
+		if (post) {
9f5db7
+		    ret = SESH_ERR_SOME_FILES;
9f5db7
+		    goto nocleanup;
9f5db7
+		} else {
9f5db7
+		    goto cleanup_0;
9f5db7
 		}
9f5db7
 	    }
9f5db7
 	}
9f5db7
diff -up ./src/sudo_edit.c.symbolic-link-attack-2 ./src/sudo_edit.c
9f5db7
--- ./src/sudo_edit.c.symbolic-link-attack-2	2021-02-02 15:31:20.554340459 +0100
9f5db7
+++ ./src/sudo_edit.c	2021-02-02 15:31:54.355884326 +0100
9f5db7
@@ -42,7 +42,6 @@
9f5db7
 #include <grp.h>
9f5db7
 #include <pwd.h>
9f5db7
 #include <signal.h>
9f5db7
-#include <errno.h>
9f5db7
 #include <fcntl.h>
9f5db7
 
9f5db7
 #include "sudo.h"
9f5db7
@@ -551,8 +550,6 @@ sudo_edit_create_tfiles(struct command_d
9f5db7
     struct tempfile *tf, char *files[], int nfiles)
9f5db7
 {
9f5db7
     int i, j, tfd, ofd, rc;
9f5db7
-    char buf[BUFSIZ];
9f5db7
-    ssize_t nwritten, nread;
9f5db7
     struct timespec times[2];
9f5db7
     struct stat sb;
9f5db7
     debug_decl(sudo_edit_create_tfiles, SUDO_DEBUG_EDIT)
9f5db7
@@ -648,18 +645,7 @@ sudo_edit_create_tfiles(struct command_d
9f5db7
 	    debug_return_int(-1);
9f5db7
 	}
9f5db7
 	if (ofd != -1) {
9f5db7
-	    while ((nread = read(ofd, buf, sizeof(buf))) > 0) {
9f5db7
-		if ((nwritten = write(tfd, buf, nread)) != nread) {
9f5db7
-		    if (nwritten == -1)
9f5db7
-			sudo_warn("%s", tf[j].tfile);
9f5db7
-		    else
9f5db7
-			sudo_warnx(U_("%s: short write"), tf[j].tfile);
9f5db7
-		    break;
9f5db7
-		}
9f5db7
-	    }
9f5db7
-	    if (nread != 0) {
9f5db7
-		if (nread < 0)
9f5db7
-		    sudo_warn("%s", files[i]);
9f5db7
+	    if (sudo_copy_file(tf[j].ofile, ofd, tf[j].osize, tf[j].tfile, tfd, -1) == -1) {
9f5db7
 		close(ofd);
9f5db7
 		close(tfd);
9f5db7
 		debug_return_int(-1);
9f5db7
@@ -689,51 +675,6 @@ sudo_edit_create_tfiles(struct command_d
9f5db7
 }
9f5db7
 
9f5db7
 /*
9f5db7
- * Extend the given fd to the specified size in bytes.
9f5db7
- * We do this to allocate disk space up-front before overwriting
9f5db7
- * the original file with the temporary.  Otherwise, we could
9f5db7
- * we run out of disk space after truncating the original file.
9f5db7
- */
9f5db7
-static int
9f5db7
-sudo_edit_extend_file(int fd, off_t new_size)
9f5db7
-{
9f5db7
-    off_t old_size, size;
9f5db7
-    ssize_t nwritten;
9f5db7
-    char zeroes[1024] = { '\0' };
9f5db7
-    debug_decl(sudo_edit_extend_file, SUDO_DEBUG_EDIT);
9f5db7
-
9f5db7
-    if ((old_size = lseek(fd, 0, SEEK_END)) == -1) {
9f5db7
-	sudo_warn("lseek");
9f5db7
-	debug_return_int(-1);
9f5db7
-    }
9f5db7
-    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: extending file from %lld to %lld",
9f5db7
-	__func__, (long long)old_size, (long long)new_size);
9f5db7
-
9f5db7
-    for (size = old_size; size < new_size; size += nwritten) {
9f5db7
-	size_t len = new_size - size;
9f5db7
-	if (len > sizeof(zeroes))
9f5db7
-	    len = sizeof(zeroes);
9f5db7
-	nwritten = write(fd, zeroes, len);
9f5db7
-	if (nwritten == -1) {
9f5db7
-	    int serrno = errno;
9f5db7
-	    if (ftruncate(fd, old_size) == -1) {
9f5db7
-		sudo_debug_printf(
9f5db7
-		    SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
9f5db7
-		    "unable to truncate to %lld", (long long)old_size);
9f5db7
-	    }
9f5db7
-	    errno = serrno;
9f5db7
-	    debug_return_int(-1);
9f5db7
-	}
9f5db7
-    }
9f5db7
-    if (lseek(fd, 0, SEEK_SET) == -1) {
9f5db7
-	sudo_warn("lseek");
9f5db7
-	debug_return_int(-1);
9f5db7
-    }
9f5db7
-
9f5db7
-    debug_return_int(0);
9f5db7
-}
9f5db7
-
9f5db7
-/*
9f5db7
  * Copy the temporary files specified in tf to the originals.
9f5db7
  * Returns the number of copy errors or 0 if completely successful.
9f5db7
  */
9f5db7
@@ -741,9 +682,7 @@ static int
9f5db7
 sudo_edit_copy_tfiles(struct command_details *command_details,
9f5db7
     struct tempfile *tf, int nfiles, struct timespec *times)
9f5db7
 {
9f5db7
-    int i, tfd, ofd, rc, errors = 0;
9f5db7
-    char buf[BUFSIZ];
9f5db7
-    ssize_t nwritten, nread;
9f5db7
+    int i, tfd, ofd, errors = 0;
9f5db7
     struct timespec ts;
9f5db7
     struct stat sb;
9f5db7
     mode_t oldmask;
9f5db7
@@ -751,7 +690,7 @@ sudo_edit_copy_tfiles(struct command_det
9f5db7
 
9f5db7
     /* Copy contents of temp files to real ones. */
9f5db7
     for (i = 0; i < nfiles; i++) {
9f5db7
-	rc = -1;
9f5db7
+	int rc = -1;
9f5db7
 	sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
9f5db7
 	    "seteuid(%u)", (unsigned int)user_details.uid);
9f5db7
 	if (seteuid(user_details.uid) != 0)
9f5db7
@@ -764,8 +703,8 @@ sudo_edit_copy_tfiles(struct command_det
9f5db7
 	    "seteuid(%u)", ROOT_UID);
9f5db7
 	if (seteuid(ROOT_UID) != 0)
9f5db7
 	    sudo_fatal("seteuid(ROOT_UID)");
9f5db7
-	if (rc || !S_ISREG(sb.st_mode)) {
9f5db7
-	    if (rc)
9f5db7
+	if (rc == -1 || !S_ISREG(sb.st_mode)) {
9f5db7
+	    if (rc == -1)
9f5db7
 		sudo_warn("%s", tf[i].tfile);
9f5db7
 	    else
9f5db7
 		sudo_warnx(U_("%s: not a regular file"), tf[i].tfile);
9f5db7
@@ -796,46 +735,19 @@ sudo_edit_copy_tfiles(struct command_det
9f5db7
 	umask(oldmask);
9f5db7
 	switch_user(ROOT_UID, user_details.egid,
9f5db7
 	    user_details.ngroups, user_details.groups);
9f5db7
-	if (ofd == -1)
9f5db7
-	    goto write_error;
9f5db7
-	/* Extend the file to the new size if larger before copying. */
9f5db7
-	if (tf[i].osize > 0 && sb.st_size > tf[i].osize) {
9f5db7
-	    if (sudo_edit_extend_file(ofd, sb.st_size) == -1)
9f5db7
-		goto write_error;
9f5db7
+	if (ofd == -1) {
9f5db7
+	    sudo_warn(U_("unable to write to %s"), tf[i].ofile);
9f5db7
+	    goto bad;
9f5db7
 	}
9f5db7
+
9f5db7
 	/* Overwrite the old file with the new contents. */
9f5db7
-	while ((nread = read(tfd, buf, sizeof(buf))) > 0) {
9f5db7
-	    ssize_t off = 0;
9f5db7
-	    do {
9f5db7
-		nwritten = write(ofd, buf + off, nread - off);
9f5db7
-		if (nwritten == -1)
9f5db7
-		    goto write_error;
9f5db7
-		off += nwritten;
9f5db7
-	    } while (nread > off);
9f5db7
-	}
9f5db7
-	if (nread == 0) {
9f5db7
-	    /* success, read to EOF */
9f5db7
-	    if (tf[i].osize > 0 && sb.st_size < tf[i].osize) {
9f5db7
-		/* We don't open with O_TRUNC so must truncate manually. */
9f5db7
-		if (ftruncate(ofd, sb.st_size)  == -1) {
9f5db7
-		    sudo_debug_printf(
9f5db7
-			SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
9f5db7
-			"unable to truncate %s to %lld", tf[i].ofile,
9f5db7
-			(long long)sb.st_size);
9f5db7
-		    goto write_error;
9f5db7
-		}
9f5db7
-	    }
9f5db7
-	    unlink(tf[i].tfile);
9f5db7
-	} else if (nread < 0) {
9f5db7
-	    sudo_warn(U_("unable to read temporary file"));
9f5db7
-	    sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
9f5db7
-	    errors++;
9f5db7
-	} else {
9f5db7
-write_error:
9f5db7
-	    sudo_warn(U_("unable to write to %s"), tf[i].ofile);
9f5db7
+	if (sudo_copy_file(tf[i].tfile, tfd, sb.st_size, tf[i].ofile, ofd,
9f5db7
+	    tf[i].osize) == -1) {
9f5db7
+bad:
9f5db7
 	    sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
9f5db7
 	    errors++;
9f5db7
 	}
9f5db7
+
9f5db7
 	if (ofd != -1)
9f5db7
 	    close(ofd);
9f5db7
 	close(tfd);
9f5db7
diff -up ./src/sudo_exec.h.symbolic-link-attack-2 ./src/sudo_exec.h
9f5db7
--- ./src/sudo_exec.h.symbolic-link-attack-2	2019-10-28 13:27:39.000000000 +0100
9f5db7
+++ ./src/sudo_exec.h	2021-02-02 15:31:20.556340432 +0100
9f5db7
@@ -84,6 +84,9 @@
9f5db7
 struct command_details;
9f5db7
 struct command_status;
9f5db7
 
9f5db7
+/* copy_file.c */
9f5db7
+int sudo_copy_file(const char *src, int src_fd, off_t src_len, const char *dst, int dst_fd, off_t dst_len);
9f5db7
+
9f5db7
 /* exec.c */
9f5db7
 void exec_cmnd(struct command_details *details, int errfd);
9f5db7
 void terminate_command(pid_t pid, bool use_pgrp);