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.
This commit is contained in:
Todd C. Miller
2020-04-17 19:08:56 -06:00
parent 2a60816f75
commit adb4360c40

View File

@@ -1,7 +1,7 @@
/* /*
* SPDX-License-Identifier: ISC * 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 * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * 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); 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. * Copy the temporary files specified in tf to the originals.
* Returns the number of copy errors or 0 if completely successful. * 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, switch_user(command_details->euid, command_details->egid,
command_details->ngroups, command_details->groups); command_details->ngroups, command_details->groups);
oldmask = umask(command_details->umask); 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); S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details);
umask(oldmask); umask(oldmask);
switch_user(ROOT_UID, user_details.egid, switch_user(ROOT_UID, user_details.egid,
user_details.ngroups, user_details.groups); user_details.ngroups, user_details.groups);
if (ofd == -1) { if (ofd == -1)
sudo_warn(U_("unable to write to %s"), tf[i].ofile); goto write_error;
sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile); /* Extend the file to the new size if larger before copying. */
close(tfd); if (tf[i].osize > 0 && sb.st_size > tf[i].osize) {
errors++; if (sudo_edit_extend_file(ofd, sb.st_size) == -1)
continue; goto write_error;
} }
/* Overwrite the old file with the new contents. */
while ((nread = read(tfd, buf, sizeof(buf))) > 0) { 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) if (nwritten == -1)
sudo_warn("%s", tf[i].ofile); goto write_error;
else off += nwritten;
sudo_warnx(U_("%s: short write"), tf[i].ofile); } while (nread > off);
break;
}
} }
if (nread == 0) { 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); unlink(tf[i].tfile);
} else if (nread < 0) { } else if (nread < 0) {
sudo_warn(U_("unable to read temporary file")); sudo_warn(U_("unable to read temporary file"));
sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile); sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
errors++;
} else { } else {
write_error:
sudo_warn(U_("unable to write to %s"), tf[i].ofile); sudo_warn(U_("unable to write to %s"), tf[i].ofile);
sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile); sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
errors++;
} }
close(ofd); if (ofd != -1)
close(ofd);
close(tfd); close(tfd);
} }
debug_return_int(errors); debug_return_int(errors);
@@ -1065,6 +1125,7 @@ cleanup:
for (i = 0; i < nfiles; i++) { for (i = 0; i < nfiles; i++) {
if (tf[i].tfile != NULL) if (tf[i].tfile != NULL)
unlink(tf[i].tfile); unlink(tf[i].tfile);
free(tf[i].tfile);
} }
} }
free(tf); free(tf);