From fd35e86fc5a7f3c13512a12e31145640cde442b3 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Fri, 27 Jun 2025 18:31:16 +0200 Subject: [PATCH] libstore: fallback on creating a safe space in the default tempdir If `settings.buildDir` cannot be written to, because we are in a chroot store, unprivileged or anything. We can and should always gracefully fallback to a *secure* location inside of /tmp, i.e. `/tmp///...`. This does not reintroduce CVE-2025-52991 because we are creating a directory in-between compared to creating only ONE level of directory. Fixes #876. Change-Id: Ie521202923f763225e1901ab1b9b6c6132aaf548 Signed-off-by: Raito Bezarius --- doc/manual/rl-next/infallible-build-dirs.md | 25 ++++++++++++ lix/libstore/build/local-derivation-goal.cc | 42 ++++++++++++++++----- lix/libstore/build/worker.hh | 1 + tests/functional/build.sh | 5 +++ 4 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 doc/manual/rl-next/infallible-build-dirs.md diff --git a/doc/manual/rl-next/infallible-build-dirs.md b/doc/manual/rl-next/infallible-build-dirs.md new file mode 100644 index 000000000..563d4fcde --- /dev/null +++ b/doc/manual/rl-next/infallible-build-dirs.md @@ -0,0 +1,25 @@ +--- +synopsis: "Fallback to safe temp dir when build-dir is unwritable" +issues: [fj#876] +cls: [3501] +category: "Fixes" +credits: ["raito", "horrors"] +--- + +Non-daemon builds started failing with a permission error after introducing the `build-dir` option: + +``` +$ nix build --store ~/scratch nixpkgs#hello --rebuild +error: creating directory '/nix/var/nix/builds/nix-build-hello-2.12.2.drv-0': Permission denied +``` + +This happens because: + +1. These builds are not run via the daemon, which owns `/nix/var/nix/builds`. +2. The user lacks permissions for that path. + +We considered making `build-dir` a store-level option and defaulting it to `/nix/var/nix/builds` for chroot stores, but opted instead for a fallback: if the default fails, Nix now creates a safe build directory under `/tmp`. + +To avoid CVE-2025-52991, the fallback uses an extra path component between `/tmp` and the build dir. + +**Note**: this fallback clutters `/tmp` with build directories that are not cleaned up. To prevent this, explicitly set `build-dir` to a path managed by Lix, even for local workloads. diff --git a/lix/libstore/build/local-derivation-goal.cc b/lix/libstore/build/local-derivation-goal.cc index 11a973d06..ab8f01f55 100644 --- a/lix/libstore/build/local-derivation-goal.cc +++ b/lix/libstore/build/local-derivation-goal.cc @@ -439,17 +439,39 @@ try { }); } - createDirs(settings.buildDir.get()); + try { + auto buildDir = worker.buildDirOverride.value_or(settings.buildDir.get()); - /* Create a temporary directory where the build will take - place. */ - tmpDir = createTempDir( - settings.buildDir.get(), - "nix-build-" + std::string(drvPath.name()), - false, - false, - 0700 - ); + createDirs(buildDir); + + /* Create a temporary directory where the build will take + place. */ + tmpDir = + createTempDir(buildDir, "nix-build-" + std::string(drvPath.name()), false, false, 0700); + } catch (SysError & e) { + /* + * Fallback to the global tmpdir and create a safe space there + * only if it's a permission error. + */ + if (e.errNo != EACCES) { + throw; + } + + auto globalTmp = defaultTempDir(); + createDirs(globalTmp); + auto nixBuildsTmp = + createTempDir(globalTmp, fmt("nix-builds-%s", geteuid()), false, false, 0700); + warn( + "Failed to use the system-wide build directory '%s', falling back to a temporary " + "directory inside '%s'", + settings.buildDir.get(), + nixBuildsTmp + ); + worker.buildDirOverride = nixBuildsTmp; + tmpDir = createTempDir( + nixBuildsTmp, "nix-build-" + std::string(drvPath.name()), false, false, 0700 + ); + } /* The TOCTOU between the previous mkdir call and this open call is unavoidable due to * POSIX semantics.*/ tmpDirFd = AutoCloseFD{open(tmpDir.c_str(), O_RDONLY | O_NOFOLLOW | O_DIRECTORY)}; diff --git a/lix/libstore/build/worker.hh b/lix/libstore/build/worker.hh index b7dcdc881..2bda6be9d 100644 --- a/lix/libstore/build/worker.hh +++ b/lix/libstore/build/worker.hh @@ -187,6 +187,7 @@ public: Store & store; Store & evalStore; AsyncSemaphore substitutions, localBuilds; + std::optional buildDirOverride; struct PlatformFeatures { diff --git a/tests/functional/build.sh b/tests/functional/build.sh index 58fba83aa..fc83f61f3 100644 --- a/tests/functional/build.sh +++ b/tests/functional/build.sh @@ -174,3 +174,8 @@ test "$(<<<"$out" grep -E '^error:' | wc -l)" = 3 <<<"$out" grepQuiet -E "error: 2 dependencies of derivation '.*-x4\\.drv' failed to build" <<<"$out" grepQuiet -vE "hash mismatch in fixed-output derivation '.*-x3\\.drv'" <<<"$out" grepQuiet -vE "hash mismatch in fixed-output derivation '.*-x2\\.drv'" + +# Ensure when if the system build dir is inaccessible, we can still build things +BUILD_DIR=$(mktemp -d) +chmod 0000 "$BUILD_DIR" +nix --build-dir "$BUILD_DIR" build -E 'with import ./config.nix; mkDerivation { name = "test"; buildCommand = "echo rawr > $out"; }' --impure --no-link