Blob Blame History Raw
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