From fa380e09918893ca7d771cce816ab742e92d8435 Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Thu, 20 Nov 2025 00:28:23 +0300 Subject: [PATCH] libutil: Make RestoreSink use *at system calls on UNIX This is necessary to ban symlink following. It can be considered a defense in depth against issues similar to CVE-2024-45593. By slightly changing the API in a follow-up commit we will be able to mitigate the symlink following issue for good. --- src/libutil/fs-sink.cc | 42 ++++++++++++++++++++++++- src/libutil/include/nix/util/fs-sink.hh | 12 +++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/libutil/fs-sink.cc b/src/libutil/fs-sink.cc index 45ef57a9f..5b5f73067 100644 --- a/src/libutil/fs-sink.cc +++ b/src/libutil/fs-sink.cc @@ -73,8 +73,34 @@ static std::filesystem::path append(const std::filesystem::path & src, const Can void RestoreSink::createDirectory(const CanonPath & path) { auto p = append(dstPath, path); + +#ifndef _WIN32 + if (dirFd) { + if (path.isRoot()) + /* Trying to create a directory that we already have a file descriptor for. */ + throw Error("path '%s' already exists", p.string()); + + if (::mkdirat(dirFd.get(), path.rel_c_str(), 0777) == -1) + throw SysError("creating directory '%s'", p.string()); + + return; + } +#endif + if (!std::filesystem::create_directory(p)) throw Error("path '%s' already exists", p.string()); + +#ifndef _WIN32 + if (path.isRoot()) { + assert(!dirFd); // Handled above + + /* Open directory for further *at operations relative to the sink root + directory. */ + dirFd = open(p.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (!dirFd) + throw SysError("creating directory '%1%'", p.string()); + } +#endif }; struct RestoreRegularFile : CreateRegularFileSink @@ -114,7 +140,14 @@ void RestoreSink::createRegularFile(const CanonPath & path, std::function '%2%'", p.string(), target); + return; + } +#endif nix::createSymlink(target, p.string()); } diff --git a/src/libutil/include/nix/util/fs-sink.hh b/src/libutil/include/nix/util/fs-sink.hh index bd2db7f53..132658b5d 100644 --- a/src/libutil/include/nix/util/fs-sink.hh +++ b/src/libutil/include/nix/util/fs-sink.hh @@ -82,6 +82,18 @@ struct NullFileSystemObjectSink : FileSystemObjectSink struct RestoreSink : FileSystemObjectSink { std::filesystem::path dstPath; +#ifndef _WIN32 + /** + * File descriptor for the directory located at dstPath. Used for *at + * operations relative to this file descriptor. This sink must *never* + * follow intermediate symlinks (starting from dstPath) in case a file + * collision is encountered for various reasons like case-insensitivity or + * other types on normalization. using appropriate *at system calls and traversing + * only one path component at a time ensures that writing is race-free and is + * is not susceptible to symlink replacement. + */ + AutoCloseFD dirFd; +#endif bool startFsync = false; explicit RestoreSink(bool startFsync)