9f5db7
From adb4360c40df99238c17c3ecedcb1d32d76e2b2e Mon Sep 17 00:00:00 2001
9f5db7
From: "Todd C. Miller" <Todd.Miller@sudo.ws>
9f5db7
Date: Fri, 17 Apr 2020 19:08:56 -0600
9f5db7
Subject: [PATCH] Extend the original file before to the new size before
9f5db7
 updating it. Instead of opening the original file for writing w/ tuncation,
9f5db7
 we first extend the file with zeroes (by writing, not seeking), then
9f5db7
 overwrite it.  This should allow sudo to fail early if the disk is out of
9f5db7
 space before it overwrites the original file.
9f5db7
9f5db7
---
9f5db7
 src/sudo_edit.c | 93 ++++++++++++++++++++++++++++++++++++++++---------
9f5db7
 1 file changed, 77 insertions(+), 16 deletions(-)
9f5db7
9f5db7
diff --git a/src/sudo_edit.c b/src/sudo_edit.c
9f5db7
index 28f6c6100..d99a5658a 100644
9f5db7
--- a/src/sudo_edit.c
9f5db7
+++ b/src/sudo_edit.c
9f5db7
@@ -1,7 +1,7 @@
9f5db7
 /*
9f5db7
  * SPDX-License-Identifier: ISC
9f5db7
  *
9f5db7
- * Copyright (c) 2004-2008, 2010-2018 Todd C. Miller <Todd.Miller@sudo.ws>
9f5db7
+ * Copyright (c) 2004-2008, 2010-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
@@ -650,6 +650,51 @@ sudo_edit_create_tfiles(struct command_details *command_details,
9f5db7
     debug_return_int(j);
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
@@ -708,38 +753,53 @@ sudo_edit_copy_tfiles(struct command_details *command_details,
9f5db7
 	switch_user(command_details->euid, command_details->egid,
9f5db7
 	    command_details->ngroups, command_details->groups);
9f5db7
 	oldmask = umask(command_details->umask);
9f5db7
-	ofd = sudo_edit_open(tf[i].ofile, O_WRONLY|O_TRUNC|O_CREAT,
9f5db7
+	ofd = sudo_edit_open(tf[i].ofile, O_WRONLY|O_CREAT,
9f5db7
 	    S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details);
9f5db7
 	umask(oldmask);
9f5db7
 	switch_user(ROOT_UID, user_details.egid,
9f5db7
 	    user_details.ngroups, user_details.groups);
9f5db7
-	if (ofd == -1) {
9f5db7
-	    sudo_warn(U_("unable to write to %s"), tf[i].ofile);
9f5db7
-	    sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
9f5db7
-	    close(tfd);
9f5db7
-	    errors++;
9f5db7
-	    continue;
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
 	}
9f5db7
+	/* Overwrite the old file with the new contents. */
9f5db7
 	while ((nread = read(tfd, buf, sizeof(buf))) > 0) {
9f5db7
-	    if ((nwritten = write(ofd, buf, nread)) != nread) {
9f5db7
+	    ssize_t off = 0;
9f5db7
+	    do {
9f5db7
+		nwritten = write(ofd, buf + off, nread - off);
9f5db7
 		if (nwritten == -1)
9f5db7
-		    sudo_warn("%s", tf[i].ofile);
9f5db7
-		else
9f5db7
-		    sudo_warnx(U_("%s: short write"), tf[i].ofile);
9f5db7
-		break;
9f5db7
-	    }
9f5db7
+		    goto write_error;
9f5db7
+		off += nwritten;
9f5db7
+	    } while (nread > off);
9f5db7
 	}
9f5db7
 	if (nread == 0) {
9f5db7
-	    /* success, got EOF */
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
 	    sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
9f5db7
+	    errors++;
9f5db7
 	}
9f5db7
-	close(ofd);
9f5db7
+	if (ofd != -1)
9f5db7
+	    close(ofd);
9f5db7
 	close(tfd);
9f5db7
     }
9f5db7
     debug_return_int(errors);
9f5db7
@@ -1065,6 +1125,7 @@ cleanup:
9f5db7
 	for (i = 0; i < nfiles; i++) {
9f5db7
 	    if (tf[i].tfile != NULL)
9f5db7
 		unlink(tf[i].tfile);
9f5db7
+	    free(tf[i].tfile);
9f5db7
 	}
9f5db7
     }
9f5db7
     free(tf);
9f5db7
-- 
9f5db7
2.26.2
9f5db7