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 6e8d9480f..1d1d59a31 100644 --- a/lix/libstore/build/local-derivation-goal.cc +++ b/lix/libstore/build/local-derivation-goal.cc @@ -483,17 +483,47 @@ 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); +#if __APPLE__ + /* macOS filesystem namespacing does not exist, to avoid breaking builds, we need to weaken + * the mode bits on the top-level directory. This avoids issues like + * https://github.com/NixOS/nix/pull/11031. */ + constexpr int toplevelDirMode = 0755; +#else + constexpr int toplevelDirMode = 0700; +#endif + auto nixBuildsTmp = + createTempDir(globalTmp, fmt("nix-builds-%s", geteuid()), false, false, toplevelDirMode); + 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 7fc3d1fe9..d9dc36e34 100644 --- a/lix/libstore/build/worker.hh +++ b/lix/libstore/build/worker.hh @@ -195,6 +195,7 @@ public: Store & store; Store & evalStore; AsyncSemaphore substitutions, localBuilds; + std::optional buildDirOverride; private: kj::TaskSet children; 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