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:
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)
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue