230a1d
From adb4360c40df99238c17c3ecedcb1d32d76e2b2e Mon Sep 17 00:00:00 2001
230a1d
From: "Todd C. Miller" <Todd.Miller@sudo.ws>
230a1d
Date: Fri, 17 Apr 2020 19:08:56 -0600
230a1d
Subject: [PATCH] Extend the original file before to the new size before
230a1d
 updating it. Instead of opening the original file for writing w/ tuncation,
230a1d
 we first extend the file with zeroes (by writing, not seeking), then
230a1d
 overwrite it.  This should allow sudo to fail early if the disk is out of
230a1d
 space before it overwrites the original file.
230a1d
230a1d
---
230a1d
 src/sudo_edit.c | 93 ++++++++++++++++++++++++++++++++++++++++---------
230a1d
 1 file changed, 77 insertions(+), 16 deletions(-)
230a1d
230a1d
diff --git a/src/sudo_edit.c b/src/sudo_edit.c
230a1d
index 28f6c6100..d99a5658a 100644
230a1d
--- a/src/sudo_edit.c
230a1d
+++ b/src/sudo_edit.c
230a1d
@@ -1,7 +1,7 @@
230a1d
 /*
230a1d
  * SPDX-License-Identifier: ISC
230a1d
  *
230a1d
- * Copyright (c) 2004-2008, 2010-2018 Todd C. Miller <Todd.Miller@sudo.ws>
230a1d
+ * Copyright (c) 2004-2008, 2010-2020 Todd C. Miller <Todd.Miller@sudo.ws>
230a1d
  *
230a1d
  * Permission to use, copy, modify, and distribute this software for any
230a1d
  * purpose with or without fee is hereby granted, provided that the above
230a1d
@@ -650,6 +650,51 @@ sudo_edit_create_tfiles(struct command_details *command_details,
230a1d
     debug_return_int(j);
230a1d
 }
230a1d
 
230a1d
+/*
230a1d
+ * Extend the given fd to the specified size in bytes.
230a1d
+ * We do this to allocate disk space up-front before overwriting
230a1d
+ * the original file with the temporary.  Otherwise, we could
230a1d
+ * we run out of disk space after truncating the original file.
230a1d
+ */
230a1d
+static int
230a1d
+sudo_edit_extend_file(int fd, off_t new_size)
230a1d
+{
230a1d
+    off_t old_size, size;
230a1d
+    ssize_t nwritten;
230a1d
+    char zeroes[1024] = { '\0' };
230a1d
+    debug_decl(sudo_edit_extend_file, SUDO_DEBUG_EDIT);
230a1d
+
230a1d
+    if ((old_size = lseek(fd, 0, SEEK_END)) == -1) {
230a1d
+	sudo_warn("lseek");
230a1d
+	debug_return_int(-1);
230a1d
+    }
230a1d
+    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: extending file from %lld to %lld",
230a1d
+	__func__, (long long)old_size, (long long)new_size);
230a1d
+
230a1d
+    for (size = old_size; size < new_size; size += nwritten) {
230a1d
+	size_t len = new_size - size;
230a1d
+	if (len > sizeof(zeroes))
230a1d
+	    len = sizeof(zeroes);
230a1d
+	nwritten = write(fd, zeroes, len);
230a1d
+	if (nwritten == -1) {
230a1d
+	    int serrno = errno;
230a1d
+	    if (ftruncate(fd, old_size) == -1) {
230a1d
+		sudo_debug_printf(
230a1d
+		    SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
230a1d
+		    "unable to truncate to %lld", (long long)old_size);
230a1d
+	    }
230a1d
+	    errno = serrno;
230a1d
+	    debug_return_int(-1);
230a1d
+	}
230a1d
+    }
230a1d
+    if (lseek(fd, 0, SEEK_SET) == -1) {
230a1d
+	sudo_warn("lseek");
230a1d
+	debug_return_int(-1);
230a1d
+    }
230a1d
+
230a1d
+    debug_return_int(0);
230a1d
+}
230a1d
+
230a1d
 /*
230a1d
  * Copy the temporary files specified in tf to the originals.
230a1d
  * Returns the number of copy errors or 0 if completely successful.
230a1d
@@ -708,38 +753,53 @@ sudo_edit_copy_tfiles(struct command_details *command_details,
230a1d
 	switch_user(command_details->euid, command_details->egid,
230a1d
 	    command_details->ngroups, command_details->groups);
230a1d
 	oldmask = umask(command_details->umask);
230a1d
-	ofd = sudo_edit_open(tf[i].ofile, O_WRONLY|O_TRUNC|O_CREAT,
230a1d
+	ofd = sudo_edit_open(tf[i].ofile, O_WRONLY|O_CREAT,
230a1d
 	    S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details);
230a1d
 	umask(oldmask);
230a1d
 	switch_user(ROOT_UID, user_details.egid,
230a1d
 	    user_details.ngroups, user_details.groups);
230a1d
-	if (ofd == -1) {
230a1d
-	    sudo_warn(U_("unable to write to %s"), tf[i].ofile);
230a1d
-	    sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
230a1d
-	    close(tfd);
230a1d
-	    errors++;
230a1d
-	    continue;
230a1d
+	if (ofd == -1)
230a1d
+	    goto write_error;
230a1d
+	/* Extend the file to the new size if larger before copying. */
230a1d
+	if (tf[i].osize > 0 && sb.st_size > tf[i].osize) {
230a1d
+	    if (sudo_edit_extend_file(ofd, sb.st_size) == -1)
230a1d
+		goto write_error;
230a1d
 	}
230a1d
+	/* Overwrite the old file with the new contents. */
230a1d
 	while ((nread = read(tfd, buf, sizeof(buf))) > 0) {
230a1d
-	    if ((nwritten = write(ofd, buf, nread)) != nread) {
230a1d
+	    ssize_t off = 0;
230a1d
+	    do {
230a1d
+		nwritten = write(ofd, buf + off, nread - off);
230a1d
 		if (nwritten == -1)
230a1d
-		    sudo_warn("%s", tf[i].ofile);
230a1d
-		else
230a1d
-		    sudo_warnx(U_("%s: short write"), tf[i].ofile);
230a1d
-		break;
230a1d
-	    }
230a1d
+		    goto write_error;
230a1d
+		off += nwritten;
230a1d
+	    } while (nread > off);
230a1d
 	}
230a1d
 	if (nread == 0) {
230a1d
-	    /* success, got EOF */
230a1d
+	    /* success, read to EOF */
230a1d
+	    if (tf[i].osize > 0 && sb.st_size < tf[i].osize) {
230a1d
+		/* We don't open with O_TRUNC so must truncate manually. */
230a1d
+		if (ftruncate(ofd, sb.st_size)  == -1) {
230a1d
+		    sudo_debug_printf(
230a1d
+			SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
230a1d
+			"unable to truncate %s to %lld", tf[i].ofile,
230a1d
+			(long long)sb.st_size);
230a1d
+		    goto write_error;
230a1d
+		}
230a1d
+	    }
230a1d
 	    unlink(tf[i].tfile);
230a1d
 	} else if (nread < 0) {
230a1d
 	    sudo_warn(U_("unable to read temporary file"));
230a1d
 	    sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
230a1d
+	    errors++;
230a1d
 	} else {
230a1d
+write_error:
230a1d
 	    sudo_warn(U_("unable to write to %s"), tf[i].ofile);
230a1d
 	    sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
230a1d
+	    errors++;
230a1d
 	}
230a1d
-	close(ofd);
230a1d
+	if (ofd != -1)
230a1d
+	    close(ofd);
230a1d
 	close(tfd);
230a1d
     }
230a1d
     debug_return_int(errors);
230a1d
@@ -1065,6 +1125,7 @@ cleanup:
230a1d
 	for (i = 0; i < nfiles; i++) {
230a1d
 	    if (tf[i].tfile != NULL)
230a1d
 		unlink(tf[i].tfile);
230a1d
+	    free(tf[i].tfile);
230a1d
 	}
230a1d
     }
230a1d
     free(tf);
230a1d
-- 
230a1d
2.26.2
230a1d