Avoid TOCTOU in sudo_mkdir_parents() using openat(2) and mkdirat(2).
This also allows us to make path const as it should be.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (c) 2009-2017 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
* Copyright (c) 2009-2021 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
|
||||
@@ -33,6 +33,7 @@
|
||||
#endif /* HAVE_STDBOOL_H */
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
@@ -44,68 +45,111 @@
|
||||
|
||||
/*
|
||||
* Create any parent directories needed by path (but not path itself).
|
||||
* Note that path is modified but is restored before it returns.
|
||||
*/
|
||||
bool
|
||||
sudo_mkdir_parents_v1(char *path, uid_t uid, gid_t gid, mode_t mode, bool quiet)
|
||||
sudo_mkdir_parents_v1(const char *path, uid_t uid, gid_t gid, mode_t mode, bool quiet)
|
||||
{
|
||||
char *slash = path;
|
||||
const char *cp, *ep, *pathend;
|
||||
bool ret = false;
|
||||
int parentfd;
|
||||
debug_decl(sudo_mkdir_parents, SUDO_DEBUG_UTIL);
|
||||
|
||||
while ((slash = strchr(slash + 1, '/')) != NULL) {
|
||||
struct stat sb;
|
||||
int dfd;
|
||||
/* Starting parent dir is either root or cwd. */
|
||||
cp = path;
|
||||
if (*cp == '/') {
|
||||
do {
|
||||
cp++;
|
||||
} while (*cp == '/');
|
||||
parentfd = open("/", O_RDONLY|O_NONBLOCK);
|
||||
} else {
|
||||
parentfd = open(".", O_RDONLY|O_NONBLOCK);
|
||||
}
|
||||
if (parentfd == -1) {
|
||||
if (!quiet)
|
||||
sudo_warn(U_("unable to open %s"), *path == '/' ? "/" : ".");
|
||||
debug_return_bool(false);
|
||||
}
|
||||
|
||||
/* Iterate over path components, skipping the last one. */
|
||||
pathend = cp + strlen(cp);
|
||||
for (cp = sudo_strsplit(cp, pathend, "/", &ep); cp != NULL && ep != NULL;
|
||||
cp = sudo_strsplit(NULL, pathend, "/", &ep)) {
|
||||
|
||||
struct stat sb;
|
||||
char name[MAXNAMLEN + 1];
|
||||
int dfd, len;
|
||||
|
||||
*slash = '\0';
|
||||
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
|
||||
"mkdir %s, mode 0%o, uid %d, gid %d", path, (unsigned int)mode,
|
||||
(int)uid, (int)gid);
|
||||
"mkdir %.*s, mode 0%o, uid %d, gid %d", (int)(ep - path),
|
||||
path, (unsigned int)mode, (int)uid, (int)gid);
|
||||
len = snprintf(name, sizeof(name), "%.*s", (int)(ep - cp), cp);
|
||||
if (len >= ssizeof(name)) {
|
||||
errno = ENAMETOOLONG;
|
||||
if (!quiet)
|
||||
sudo_warn(U_("unable to open %.*s"), (int)(ep - path), path);
|
||||
goto done;
|
||||
}
|
||||
reopen:
|
||||
dfd = open(path, O_RDONLY|O_NONBLOCK);
|
||||
dfd = openat(parentfd, name, O_RDONLY|O_NONBLOCK, 0);
|
||||
if (dfd == -1) {
|
||||
if (errno != ENOENT) {
|
||||
if (!quiet)
|
||||
sudo_warn(U_("unable to open %s"), path);
|
||||
goto bad;
|
||||
if (!quiet) {
|
||||
sudo_warn(U_("unable to open %.*s"),
|
||||
(int)(ep - path), path);
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
if (mkdir(path, mode) == 0) {
|
||||
if (mkdirat(parentfd, name, mode) == 0) {
|
||||
dfd = openat(parentfd, name, O_RDONLY|O_NONBLOCK, 0);
|
||||
if (dfd == -1) {
|
||||
if (!quiet) {
|
||||
sudo_warn(U_("unable to open %.*s"),
|
||||
(int)(ep - path), path);
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
if (uid != (uid_t)-1 && gid != (gid_t)-1) {
|
||||
if (chown(path, uid, gid) != 0) {
|
||||
if (fchown(dfd, uid, gid) != 0) {
|
||||
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
|
||||
"%s: unable to chown %d:%d %s", __func__,
|
||||
(int)uid, (int)gid, path);
|
||||
"%s: unable to chown %d:%d %.*s", __func__,
|
||||
(int)uid, (int)gid, (int)(ep - path), path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (errno == EEXIST)
|
||||
goto reopen;
|
||||
if (!quiet)
|
||||
sudo_warn(U_("unable to mkdir %s"), path);
|
||||
goto bad;
|
||||
if (!quiet) {
|
||||
sudo_warn(U_("unable to mkdir %.*s"),
|
||||
(int)(ep - path), path);
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
} else {
|
||||
/* Already exists, make sure it is a directory. */
|
||||
int rc = fstat(dfd, &sb);
|
||||
close(dfd);
|
||||
if (rc != 0) {
|
||||
if (!quiet)
|
||||
sudo_warn(U_("unable to stat %s"), path);
|
||||
goto bad;
|
||||
if (fstat(dfd, &sb) != 0) {
|
||||
if (!quiet) {
|
||||
sudo_warn(U_("unable to stat %.*s"),
|
||||
(int)(ep - path), path);
|
||||
}
|
||||
close(dfd);
|
||||
goto done;
|
||||
}
|
||||
if (!S_ISDIR(sb.st_mode)) {
|
||||
if (!quiet)
|
||||
sudo_warnx(U_("%s exists but is not a directory (0%o)"),
|
||||
path, (unsigned int) sb.st_mode);
|
||||
goto bad;
|
||||
if (!quiet) {
|
||||
sudo_warnx(U_("%.*s exists but is not a directory (0%o)"),
|
||||
(int)(ep - path), path, (unsigned int) sb.st_mode);
|
||||
}
|
||||
close(dfd);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
*slash = '/';
|
||||
close(parentfd);
|
||||
parentfd = dfd;
|
||||
}
|
||||
ret = true;
|
||||
|
||||
debug_return_bool(true);
|
||||
bad:
|
||||
/* We must restore the path before we return. */
|
||||
/* cppcheck-suppress nullPointerRedundantCheck */
|
||||
*slash = '/';
|
||||
debug_return_bool(false);
|
||||
done:
|
||||
if (parentfd != -1)
|
||||
close(parentfd);
|
||||
debug_return_bool(ret);
|
||||
}
|
||||
|
Reference in New Issue
Block a user