mirror of
https://github.com/NixOS/nix.git
synced 2025-11-22 10:19: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:
parent
533cced249
commit
fa380e0991
2 changed files with 53 additions and 1 deletions
|
|
@ -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<void(C
|
|||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL)
|
||||
#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
|
||||
;
|
||||
if (!crf.fd)
|
||||
|
|
@ -161,6 +194,13 @@ void RestoreRegularFile::operator()(std::string_view data)
|
|||
void RestoreSink::createSymlink(const CanonPath & path, const std::string & target)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue