Merge "libstore: replace random() calls with atomic counter" into main

This commit is contained in:
Lily Ballard 2024-11-07 00:15:23 +00:00 committed by Gerrit Code Review
commit 72cce7be3f
8 changed files with 51 additions and 14 deletions

View file

@ -0,0 +1,13 @@
---
synopsis: Fix potential store corruption with auto-optimise-store
issues: [7273]
cls: [2100]
category: Fixes
credits: lilyball
---
Optimising store paths (and other operations involving temporary files) no longer use `random(3)`
to generate filenames. On darwin systems this was observed to potentially cause store corruption
when using [`auto-optimise-store`](@docroot@/command-ref/conf-file.md#conf-auto-optimise-store),
though this corruption was possible on any system whose `random(3)` does not have locking around
the global state.

View file

@ -172,12 +172,6 @@ void initNix()
now. In particular, store objects should be readable by now. In particular, store objects should be readable by
everybody. */ everybody. */
umask(0022); umask(0022);
/* Initialise the PRNG. */
struct timeval tv;
gettimeofday(&tv, 0);
srandom(tv.tv_usec);
} }

View file

@ -1,4 +1,5 @@
#include "derivation-goal.hh" #include "derivation-goal.hh"
#include "file-system.hh"
#include "hook-instance.hh" #include "hook-instance.hh"
#include "worker.hh" #include "worker.hh"
#include "finally.hh" #include "finally.hh"
@ -860,23 +861,31 @@ void replaceValidPath(const Path & storePath, const Path & tmpPath)
tmpPath (the replacement), so we have to move it out of the tmpPath (the replacement), so we have to move it out of the
way first. We'd better not be interrupted here, because if way first. We'd better not be interrupted here, because if
we're repairing (say) Glibc, we end up with a broken system. */ we're repairing (say) Glibc, we end up with a broken system. */
Path oldPath = fmt("%1%.old-%2%-%3%", storePath, getpid(), random()); Path oldPath;
if (pathExists(storePath)) if (pathExists(storePath)) {
do {
oldPath = makeTempPath(storePath, ".old");
// store paths are often directories so we can't just unlink() it
// let's make sure the path doesn't exist before we try to use it
} while (pathExists(oldPath));
movePath(storePath, oldPath); movePath(storePath, oldPath);
}
try { try {
movePath(tmpPath, storePath); movePath(tmpPath, storePath);
} catch (...) { } catch (...) {
try { try {
// attempt to recover // attempt to recover
movePath(oldPath, storePath); if (!oldPath.empty())
movePath(oldPath, storePath);
} catch (...) { } catch (...) {
ignoreExceptionExceptInterrupt(); ignoreExceptionExceptInterrupt();
} }
throw; throw;
} }
deletePath(oldPath); if (!oldPath.empty())
deletePath(oldPath);
} }

View file

@ -33,7 +33,8 @@ static void makeSymlink(const Path & link, const Path & target)
createDirs(dirOf(link)); createDirs(dirOf(link));
/* Create the new symlink. */ /* Create the new symlink. */
Path tempLink = fmt("%1%.tmp-%2%-%3%", link, getpid(), random()); Path tempLink = makeTempPath(link);
unlink(tempLink.c_str()); // just in case; ignore errors
createSymlink(target, tempLink); createSymlink(target, tempLink);
/* Atomically replace the old one. */ /* Atomically replace the old one. */

View file

@ -215,7 +215,8 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
its timestamp back to 0. */ its timestamp back to 0. */
MakeReadOnly makeReadOnly(mustToggle ? dirOfPath : ""); MakeReadOnly makeReadOnly(mustToggle ? dirOfPath : "");
Path tempLink = fmt("%1%/.tmp-link-%2%-%3%", realStoreDir, getpid(), random()); Path tempLink = makeTempPath(realStoreDir, "/.tmp-link");
unlink(tempLink.c_str()); // just in case; ignore errors
if (link(linkPath.c_str(), tempLink.c_str()) == -1) { if (link(linkPath.c_str(), tempLink.c_str()) == -1) {
if (errno == EMLINK) { if (errno == EMLINK) {
@ -234,7 +235,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
renameFile(tempLink, path); renameFile(tempLink, path);
} catch (SysError & e) { } catch (SysError & e) {
if (unlink(tempLink.c_str()) == -1) if (unlink(tempLink.c_str()) == -1)
printError("unable to unlink '%1%'", tempLink); printError("unable to unlink '%1%': %2%", tempLink, strerror(errno));
if (errno == EMLINK) { if (errno == EMLINK) {
/* Some filesystems generate too many links on the rename, /* Some filesystems generate too many links on the rename,
rather than on the original link. (Probably it rather than on the original link. (Probably it

View file

@ -5,6 +5,7 @@
#include "signals.hh" #include "signals.hh"
#include "url.hh" #include "url.hh"
#include <random>
#include <sqlite3.h> #include <sqlite3.h>
@ -265,9 +266,11 @@ void handleSQLiteBusy(const SQLiteBusy & e, time_t & nextWarning)
/* Sleep for a while since retrying the transaction right away /* Sleep for a while since retrying the transaction right away
is likely to fail again. */ is likely to fail again. */
checkInterrupt(); checkInterrupt();
static thread_local std::default_random_engine generator(clock());
std::uniform_int_distribution<long> uniform_dist(0, 100);
struct timespec t; struct timespec t;
t.tv_sec = 0; t.tv_sec = 0;
t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */ t.tv_nsec = uniform_dist(generator) * 1000 * 1000; /* <= 0.1s */
nanosleep(&t, 0); nanosleep(&t, 0);
} }

View file

@ -2,6 +2,7 @@
#include <cstdlib> #include <cstdlib>
#include <filesystem> #include <filesystem>
#include <atomic> #include <atomic>
#include <random>
#include "environment-variables.hh" #include "environment-variables.hh"
#include "file-descriptor.hh" #include "file-descriptor.hh"
@ -617,6 +618,13 @@ std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
return {std::move(fd), tmpl}; return {std::move(fd), tmpl};
} }
Path makeTempPath(const Path & root, const Path & suffix)
{
// start the counter at a random value to minimize issues with preexisting temp paths
static std::atomic_uint_fast32_t counter(std::random_device{}());
return fmt("%1%%2%-%3%-%4%", root, suffix, getpid(), counter.fetch_add(1, std::memory_order_relaxed));
}
void createSymlink(const Path & target, const Path & link) void createSymlink(const Path & target, const Path & link)
{ {
if (symlink(target.c_str(), link.c_str())) if (symlink(target.c_str(), link.c_str()))

View file

@ -303,6 +303,14 @@ std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix = "nix");
*/ */
Path defaultTempDir(); Path defaultTempDir();
/**
* Return temporary path constructed by appending a suffix to a root path.
*
* The constructed path looks like `<root><suffix>-<pid>-<unique>`. To create a
* path nested in a directory, provide a suffix starting with `/`.
*/
Path makeTempPath(const Path & root, const Path & suffix = ".tmp");
/** /**
* Used in various places. * Used in various places.
*/ */