1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-22 18:29:36 +01:00

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.
This commit is contained in:
Sergei Zimmerman 2025-11-20 00:28:23 +03:00
parent 533cced249
commit fa380e0991
No known key found for this signature in database
2 changed files with 53 additions and 1 deletions

View file

@ -73,8 +73,34 @@ static std::filesystem::path append(const std::filesystem::path & src, const Can
void RestoreSink::createDirectory(const CanonPath & path) void RestoreSink::createDirectory(const CanonPath & path)
{ {
auto p = append(dstPath, 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)) if (!std::filesystem::create_directory(p))
throw Error("path '%s' already exists", p.string()); 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 struct RestoreRegularFile : CreateRegularFileSink
@ -114,7 +140,14 @@ void RestoreSink::createRegularFile(const CanonPath & path, std::function<void(C
FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NORMAL,
NULL) NULL)
#else #else
open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666) [&]() {
/* O_EXCL together with O_CREAT ensures symbolic links in the last
component are not followed. */
constexpr int flags = O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC;
if (!dirFd)
return ::open(p.c_str(), flags, 0666);
return ::openat(dirFd.get(), path.rel_c_str(), flags, 0666);
}();
#endif #endif
; ;
if (!crf.fd) if (!crf.fd)
@ -161,6 +194,13 @@ void RestoreRegularFile::operator()(std::string_view data)
void RestoreSink::createSymlink(const CanonPath & path, const std::string & target) void RestoreSink::createSymlink(const CanonPath & path, const std::string & target)
{ {
auto p = append(dstPath, path); auto p = append(dstPath, path);
#ifndef _WIN32
if (dirFd) {
if (::symlinkat(requireCString(target), dirFd.get(), path.rel_c_str()) == -1)
throw SysError("creating symlink from '%1%' -> '%2%'", p.string(), target);
return;
}
#endif
nix::createSymlink(target, p.string()); nix::createSymlink(target, p.string());
} }

View file

@ -82,6 +82,18 @@ struct NullFileSystemObjectSink : FileSystemObjectSink
struct RestoreSink : FileSystemObjectSink struct RestoreSink : FileSystemObjectSink
{ {
std::filesystem::path dstPath; 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; bool startFsync = false;
explicit RestoreSink(bool startFsync) explicit RestoreSink(bool startFsync)