summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorReepca Russelstein <reepca@russelstein.xyz>2025-03-28 05:55:51 -0500
committerJohn Kehayias <john.kehayias@protonmail.com>2025-06-24 10:07:55 -0400
commit7173c2c0cad8afc9d8d1ad26f345b5a04f47716a (patch)
tree7e630ab5e5d123494609bac10d07a7e7c3548593
parenta183afa8e251e86d9dc17e8f177deeef0c1d534d (diff)
daemon: Implement ‘deletePath’ in terms of the *at functions.
deletePath needs to be able to operate securely in unfriendly environments, where adversaries may be concurrently modifying the files being operated on. For example, directories that we are currently recursing through may be replaced with symbolic links. We err on the side of early failure here: if a file or directory is concurrently modified in a way that causes one of the system calls to fail, we throw an exception immediately instead of trying to adapt to the change. Note that we use fstat instead of fstatat for verifying the directory's st_mode field because AT_EMPTY_PATH is linux-specific. * nix/libutil/util.cc (_deletePathAt): new procedure. (_deletePath): use it. Change-Id: I7ccfe6f1f74dbab95617b24034494e0f63030582 Signed-off-by: Ludovic Courtès <ludo@gnu.org> Signed-off-by: John Kehayias <john.kehayias@protonmail.com>
-rw-r--r--nix/libutil/util.cc58
1 files changed, 42 insertions, 16 deletions
diff --git a/nix/libutil/util.cc b/nix/libutil/util.cc
index 74f7a97cc4..c406325cdc 100644
--- a/nix/libutil/util.cc
+++ b/nix/libutil/util.cc
@@ -323,47 +323,73 @@ void writeLine(int fd, string s)
}
-static void _deletePath(const Path & path, unsigned long long & bytesFreed, size_t linkThreshold)
+static void _deletePathAt(int fd, const Path & path, const Path & fullPath, unsigned long long & bytesFreed, size_t linkThreshold)
{
checkInterrupt();
- printMsg(lvlVomit, format("%1%") % path);
+ printMsg(lvlVomit, format("%1%") % fullPath);
#ifdef HAVE_STATX
# define st_mode stx_mode
# define st_size stx_size
# define st_nlink stx_nlink
+#define fstatat(fd, path, stat, flags) \
+ statx(fd, path, flags, STATX_SIZE | STATX_NLINK | STATX_MODE, stat)
+#define fstat(fd, stat) \
+ statx(fd, "", AT_EMPTY_PATH, STATX_SIZE | STATX_NLINK | STATX_MODE, stat)
struct statx st;
- if (statx(AT_FDCWD, path.c_str(),
- AT_SYMLINK_NOFOLLOW,
- STATX_SIZE | STATX_NLINK | STATX_MODE, &st) == -1)
- throw SysError(format("getting status of `%1%'") % path);
#else
- struct stat st = lstat(path);
+ struct stat st;
#endif
+ if (fstatat(fd, path.c_str(), &st, AT_SYMLINK_NOFOLLOW))
+ throw SysError(format("getting status of `%1%'") % fullPath);
+ /* Note: if another process modifies what is at 'path' between now and
+ when we actually delete it, this may be inaccurate, but I know of no
+ way to check which file we actually deleted after the fact. */
if (!S_ISDIR(st.st_mode) && st.st_nlink <= linkThreshold)
bytesFreed += st.st_size;
if (S_ISDIR(st.st_mode)) {
- /* Make the directory writable. */
- if (!(st.st_mode & S_IWUSR)) {
- if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
- throw SysError(format("making `%1%' writable") % path);
- }
+ /* Note: fds required scales with depth of directory nesting */
+ AutoCloseFD dirfd = openat(fd, path.c_str(),
+ O_RDONLY |
+ O_DIRECTORY |
+ O_NOFOLLOW |
+ O_CLOEXEC);
+ if(!dirfd.isOpen())
+ throw SysError(format("opening `%1%'") % fullPath);
+
+ /* st.st_mode may currently be from a different file than what we
+ actually opened, get it straight from the file instead */
+ if(fstat(dirfd, &st))
+ throw SysError(format("re-getting status of `%1'") % fullPath);
- for (auto & i : readDirectory(path))
- _deletePath(path + "/" + i.name, bytesFreed, linkThreshold);
+ /* Make the directory writable. */
+ if (!(st.st_mode & S_IWUSR)) {
+ if (fchmod(dirfd, st.st_mode | S_IWUSR) == -1)
+ throw SysError(format("making `%1%' writable") % fullPath);
+ }
+
+ for (auto & i : readDirectory(dirfd))
+ _deletePathAt(dirfd, i.name, path + "/" + i.name, bytesFreed, linkThreshold);
}
int ret;
- ret = S_ISDIR(st.st_mode) ? rmdir(path.c_str()) : unlink(path.c_str());
+ ret = unlinkat(fd, path.c_str(), S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0 );
if (ret == -1)
- throw SysError(format("cannot unlink `%1%'") % path);
+ throw SysError(format("cannot unlink `%1%'") % fullPath);
#undef st_mode
#undef st_size
#undef st_nlink
+#undef fstatat
+#undef fstat
+}
+
+static void _deletePath(const Path & path, unsigned long long & bytesFreed, size_t linkThreshold)
+{
+ _deletePathAt(AT_FDCWD, path, path, bytesFreed, linkThreshold);
}