Unverified Commit 89c040ed authored by Jörg Thalheim's avatar Jörg Thalheim Committed by GitHub
Browse files

nix: apply patches for GHSA-g3g9-5vj6-r3gj (#507672)

parents 0e028439 50eec35e
Loading
Loading
Loading
Loading
+18 −2
Original line number Diff line number Diff line
@@ -165,6 +165,7 @@ lib.makeExtensible (
            hash = "sha256-vFv/D08x9urtoIE9wiC7Lln4Eq3sgNBwU7TBE1iyrfI=";
          })
          lowdown30PatchOld
          ./patches/ghsa-g3g9-5vj6-r3gj-2.28.patch
        ];
      };

@@ -189,6 +190,7 @@ lib.makeExtensible (
                hash = "sha256-r2ZF1zBZDKMvyX6X4VsaTMrg0zdjn59Jf6Hqg56r29E=";
              })
              lowdown30PatchOld
              ./patches/ghsa-g3g9-5vj6-r3gj-2.30.patch
            ]
          );

@@ -208,6 +210,8 @@ lib.makeExtensible (
        }).appendPatches
          [
            lowdown30Patch
            ./patches/ghsa-g3g9-5vj6-r3gj-2.31.patch
            ./patches/landlock-abstract-socket-hardening-2.31.patch
          ];

      nix_2_31 = addTests "nix_2_31" self.nixComponents_2_31.nix-everything;
@@ -224,7 +228,13 @@ lib.makeExtensible (
            hash = "sha256-WPuGqMQGepXoRYjtRudMAMHEoLsIObw2x4sVfho5feA=";
          };
        }).appendPatches
          patches_common;
          (
            patches_common
            ++ [
              ./patches/ghsa-g3g9-5vj6-r3gj-2.34.patch
              ./patches/landlock-abstract-socket-hardening-2.34.patch
            ]
          );

      nix_2_34 = addTests "nix_2_34" self.nixComponents_2_34.nix-everything;

@@ -240,7 +250,13 @@ lib.makeExtensible (
            hash = "sha256-fybp46IQmRN7lEUTChc3MTqxmRutmDO4RNSPEQfJQsQ=";
          };
        }).appendPatches
          patches_common;
          (
            patches_common
            ++ [
              ./patches/ghsa-g3g9-5vj6-r3gj-git.patch
              ./patches/landlock-abstract-socket-hardening-git.patch
            ]
          );

      git = addTests "git" self.nixComponents_git.nix-everything;

+126 −0
Original line number Diff line number Diff line
From 32b09e0bfeb33434866610994d89f48da8a2bf41 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= <joerg@thalheim.io>
Date: Mon, 6 Apr 2026 16:49:13 +0200
Subject: [PATCH] Fixes for GHSA-g3g9-5vj6-r3gj
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Squashed commit of the following:

commit 716dba6692c42e301a1e769e5eac02a4d6e63150
Author: Sergei Zimmerman <sergei@zimmerman.foo>
Date:   Fri Apr 3 00:21:31 2026 +0300

    derivation-builder: Don't use copyFile for FOD output copying, put the output in a temporary directory in the store

commit a3215e7c5c260fab5f2cb034c4df01dfa3b284e5
Author: Sergei Zimmerman <sergei@zimmerman.foo>
Date:   Fri Apr 3 00:21:21 2026 +0300

    libstore: Make temporary in-store directory not world-readable

Signed-off-by: Jörg Thalheim <joerg@thalheim.io>
---
 src/libstore/include/nix/store/local-store.hh |  2 ++
 src/libstore/local-store.cc                   |  5 +--
 .../unix/build/local-derivation-goal.cc       | 36 ++++++++++++++-----
 3 files changed, 33 insertions(+), 10 deletions(-)

diff --git a/src/libstore/include/nix/store/local-store.hh b/src/libstore/include/nix/store/local-store.hh
index 5893b7d8b..c9266f6b4 100644
--- a/src/libstore/include/nix/store/local-store.hh
+++ b/src/libstore/include/nix/store/local-store.hh
@@ -408,6 +408,8 @@ private:
     friend struct PathSubstitutionGoal;
     friend struct SubstitutionGoal;
     friend struct DerivationGoal;
+    /* Only used for createTempDirInStore. */
+    friend class DerivationBuilderImpl;
 };
 
 } // namespace nix
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 63108fab4..ab3a0d034 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -1311,8 +1311,9 @@ std::pair<std::filesystem::path, AutoCloseFD> LocalStore::createTempDirInStore()
     do {
         /* There is a slight possibility that `tmpDir' gets deleted by
            the GC between createTempDir() and when we acquire a lock on it.
-           We'll repeat until 'tmpDir' exists and we've locked it. */
-        tmpDirFn = createTempDir(realStoreDir, "tmp");
+           We'll repeat until 'tmpDir' exists and we've locked it.
+           Make the directory accessible only to the current user.*/
+        tmpDirFn = createTempDir(realStoreDir, "tmp", /*mode=*/0700);
         tmpDirFd = openDirectory(tmpDirFn);
         if (!tmpDirFd) {
             continue;
diff --git a/src/libstore/unix/build/local-derivation-goal.cc b/src/libstore/unix/build/local-derivation-goal.cc
index 88c82e063..3e4c3017d 100644
--- a/src/libstore/unix/build/local-derivation-goal.cc
+++ b/src/libstore/unix/build/local-derivation-goal.cc
@@ -2547,6 +2547,13 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
         assert(output && scratchPath);
         auto actualPath = toRealPathChroot(worker.store.printStorePath(*scratchPath));
 
+        /* An optional file descriptor of a directory used for intermediate
+           operations. */
+        AutoCloseFD tempDirFd;
+        /* RAII cleanup of a temporary directory inside the store that is used
+           for intermediate operations. */
+        std::optional<AutoDelete> delTempDir;
+
         auto finish = [&](StorePath finalStorePath) {
             /* Store the final path */
             finalOutputs.insert_or_assign(outputName, finalStorePath);
@@ -2681,6 +2688,25 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
             return newInfo0;
         };
 
+        auto moveOutputToTempDir = [&]() -> void {
+            std::filesystem::path tempDir;
+            std::tie(tempDir, tempDirFd) = getLocalStore().createTempDirInStore();
+            delTempDir.emplace(tempDir);
+
+            auto tmpOutput = tempDir / "x";
+
+            /* Serialise and create a fresh copy of the output to break
+               any stale writable file descriptors. Copy through the
+               serialisation/deserialisation. TODO: Use copyRecursive here and
+               make use of reflinking. */
+            auto source = sinkToSource([&](Sink & nextSink) { dumpPath(actualPath, nextSink); });
+            restorePath(tmpOutput, *source, settings.fsyncStorePaths);
+            /* This makes it slightly harder to make sense of the control flow. The rule
+               of thumb is that actualPath points to the current location of the stuff
+               that we'll end up registering. */
+            actualPath = std::move(tmpOutput);
+        };
+
         ValidPathInfo newInfo = std::visit(
             overloaded{
 
@@ -2708,14 +2734,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
 
                 [&](const DerivationOutput::CAFixed & dof) {
                     auto & wanted = dof.ca.hash;
-
-                    // Replace the output by a fresh copy of itself to make sure
-                    // that there's no stale file descriptor pointing to it
-                    Path tmpOutput = actualPath + ".tmp";
-                    copyFile(std::filesystem::path(actualPath), std::filesystem::path(tmpOutput), true);
-
-                    std::filesystem::rename(tmpOutput, actualPath);
-
+                    moveOutputToTempDir();
                     auto newInfo0 = newInfoFromCA(
                         DerivationOutput::CAFloating{
                             .method = dof.ca.method,
@@ -2756,6 +2775,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
                 },
 
                 [&](const DerivationOutput::Impure & doi) {
+                    moveOutputToTempDir();
                     return newInfoFromCA(
                         DerivationOutput::CAFloating{
                             .method = doi.method,
+126 −0
Original line number Diff line number Diff line
From 4d0a078f1dae9a07d04e1a72e7e62fbf2ca249e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= <joerg@thalheim.io>
Date: Mon, 6 Apr 2026 16:49:13 +0200
Subject: [PATCH] Fixes for GHSA-g3g9-5vj6-r3gj
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Squashed commit of the following:

commit a8e5b27728e3b18e63c2503a83afd04031994623
Author: Sergei Zimmerman <sergei@zimmerman.foo>
Date:   Fri Apr 3 00:21:31 2026 +0300

    derivation-builder: Don't use copyFile for FOD output copying, put the output in a temporary directory in the store

commit 49ff4d6779ec20c5339b237dec9b240c3eabf535
Author: Sergei Zimmerman <sergei@zimmerman.foo>
Date:   Fri Apr 3 00:21:21 2026 +0300

    libstore: Make temporary in-store directory not world-readable

Signed-off-by: Jörg Thalheim <joerg@thalheim.io>
---
 src/libstore/include/nix/store/local-store.hh |  2 ++
 src/libstore/local-store.cc                   |  5 +--
 src/libstore/unix/build/derivation-builder.cc | 36 ++++++++++++++-----
 3 files changed, 33 insertions(+), 10 deletions(-)

diff --git a/src/libstore/include/nix/store/local-store.hh b/src/libstore/include/nix/store/local-store.hh
index 5f3a249f8..74fa1c2e8 100644
--- a/src/libstore/include/nix/store/local-store.hh
+++ b/src/libstore/include/nix/store/local-store.hh
@@ -446,6 +446,8 @@ private:
     friend struct PathSubstitutionGoal;
     friend struct SubstitutionGoal;
     friend struct DerivationGoal;
+    /* Only used for createTempDirInStore. */
+    friend class DerivationBuilderImpl;
 };
 
 } // namespace nix
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 49c499e3f..5f897856f 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -1316,8 +1316,9 @@ std::pair<std::filesystem::path, AutoCloseFD> LocalStore::createTempDirInStore()
     do {
         /* There is a slight possibility that `tmpDir' gets deleted by
            the GC between createTempDir() and when we acquire a lock on it.
-           We'll repeat until 'tmpDir' exists and we've locked it. */
-        tmpDirFn = createTempDir(config->realStoreDir, "tmp");
+           We'll repeat until 'tmpDir' exists and we've locked it.
+           Make the directory accessible only to the current user.*/
+        tmpDirFn = createTempDir(config->realStoreDir, "tmp", /*mode=*/0700);
         tmpDirFd = openDirectory(tmpDirFn);
         if (!tmpDirFd) {
             continue;
diff --git a/src/libstore/unix/build/derivation-builder.cc b/src/libstore/unix/build/derivation-builder.cc
index 5f9dafb57..609d88a79 100644
--- a/src/libstore/unix/build/derivation-builder.cc
+++ b/src/libstore/unix/build/derivation-builder.cc
@@ -1581,6 +1581,13 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
         assert(output && scratchPath);
         auto actualPath = realPathInSandbox(store.printStorePath(*scratchPath));
 
+        /* An optional file descriptor of a directory used for intermediate
+           operations. */
+        AutoCloseFD tempDirFd;
+        /* RAII cleanup of a temporary directory inside the store that is used
+           for intermediate operations. */
+        std::optional<AutoDelete> delTempDir;
+
         auto finish = [&](StorePath finalStorePath) {
             /* Store the final path */
             finalOutputs.insert_or_assign(outputName, finalStorePath);
@@ -1715,6 +1722,25 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
             return newInfo0;
         };
 
+        auto moveOutputToTempDir = [&]() -> void {
+            std::filesystem::path tempDir;
+            std::tie(tempDir, tempDirFd) = getLocalStore(store).createTempDirInStore();
+            delTempDir.emplace(tempDir);
+
+            auto tmpOutput = tempDir / "x";
+
+            /* Serialise and create a fresh copy of the output to break
+               any stale writable file descriptors. Copy through the
+               serialisation/deserialisation. TODO: Use copyRecursive here and
+               make use of reflinking. */
+            auto source = sinkToSource([&](Sink & nextSink) { dumpPath(actualPath, nextSink); });
+            restorePath(tmpOutput, *source, settings.fsyncStorePaths);
+            /* This makes it slightly harder to make sense of the control flow. The rule
+               of thumb is that actualPath points to the current location of the stuff
+               that we'll end up registering. */
+            actualPath = std::move(tmpOutput);
+        };
+
         ValidPathInfo newInfo = std::visit(
             overloaded{
 
@@ -1742,14 +1768,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
 
                 [&](const DerivationOutput::CAFixed & dof) {
                     auto & wanted = dof.ca.hash;
-
-                    // Replace the output by a fresh copy of itself to make sure
-                    // that there's no stale file descriptor pointing to it
-                    Path tmpOutput = actualPath + ".tmp";
-                    copyFile(std::filesystem::path(actualPath), std::filesystem::path(tmpOutput), true);
-
-                    std::filesystem::rename(tmpOutput, actualPath);
-
+                    moveOutputToTempDir();
                     auto newInfo0 = newInfoFromCA(
                         DerivationOutput::CAFloating{
                             .method = dof.ca.method,
@@ -1790,6 +1809,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
                 },
 
                 [&](const DerivationOutput::Impure & doi) {
+                    moveOutputToTempDir();
                     return newInfoFromCA(
                         DerivationOutput::CAFloating{
                             .method = doi.method,
+126 −0
Original line number Diff line number Diff line
From df0153a9eca42c4ed5ac784657c8c5d0664c9e0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= <joerg@thalheim.io>
Date: Mon, 6 Apr 2026 16:49:13 +0200
Subject: [PATCH] Fixes for GHSA-g3g9-5vj6-r3gj
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Squashed commit of the following:

commit bf1d95ae399e47daeafa9e0dfac967f415c52447
Author: Sergei Zimmerman <sergei@zimmerman.foo>
Date:   Fri Apr 3 00:21:31 2026 +0300

    derivation-builder: Don't use copyFile for FOD output copying, put the output in a temporary directory in the store

commit ca5986fa353d402738acf961bcaa017c61019809
Author: Sergei Zimmerman <sergei@zimmerman.foo>
Date:   Fri Apr 3 00:21:21 2026 +0300

    libstore: Make temporary in-store directory not world-readable

Signed-off-by: Jörg Thalheim <joerg@thalheim.io>
---
 src/libstore/include/nix/store/local-store.hh |  2 ++
 src/libstore/local-store.cc                   |  5 +--
 src/libstore/unix/build/derivation-builder.cc | 36 ++++++++++++++-----
 3 files changed, 33 insertions(+), 10 deletions(-)

diff --git a/src/libstore/include/nix/store/local-store.hh b/src/libstore/include/nix/store/local-store.hh
index ec88ffb4f..91df9cd77 100644
--- a/src/libstore/include/nix/store/local-store.hh
+++ b/src/libstore/include/nix/store/local-store.hh
@@ -455,6 +455,8 @@ private:
 
     friend struct PathSubstitutionGoal;
     friend struct DerivationGoal;
+    /* Only used for createTempDirInStore. */
+    friend class DerivationBuilderImpl;
 };
 
 } // namespace nix
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index d24bc415c..524a9fdda 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -1342,8 +1342,9 @@ std::pair<std::filesystem::path, AutoCloseFD> LocalStore::createTempDirInStore()
     do {
         /* There is a slight possibility that `tmpDir' gets deleted by
            the GC between createTempDir() and when we acquire a lock on it.
-           We'll repeat until 'tmpDir' exists and we've locked it. */
-        tmpDirFn = createTempDir(config->realStoreDir, "tmp");
+           We'll repeat until 'tmpDir' exists and we've locked it.
+           Make the directory accessible only to the current user.*/
+        tmpDirFn = createTempDir(config->realStoreDir, "tmp", /*mode=*/0700);
         tmpDirFd = openDirectory(tmpDirFn);
         if (!tmpDirFd) {
             continue;
diff --git a/src/libstore/unix/build/derivation-builder.cc b/src/libstore/unix/build/derivation-builder.cc
index 73bb026a2..d11f80159 100644
--- a/src/libstore/unix/build/derivation-builder.cc
+++ b/src/libstore/unix/build/derivation-builder.cc
@@ -1473,6 +1473,13 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
         assert(output && scratchPath);
         auto actualPath = realPathInSandbox(store.printStorePath(*scratchPath));
 
+        /* An optional file descriptor of a directory used for intermediate
+           operations. */
+        AutoCloseFD tempDirFd;
+        /* RAII cleanup of a temporary directory inside the store that is used
+           for intermediate operations. */
+        std::optional<AutoDelete> delTempDir;
+
         auto finish = [&](StorePath finalStorePath) {
             /* Store the final path */
             finalOutputs.insert_or_assign(outputName, finalStorePath);
@@ -1607,6 +1614,25 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
             return newInfo0;
         };
 
+        auto moveOutputToTempDir = [&]() -> void {
+            std::filesystem::path tempDir;
+            std::tie(tempDir, tempDirFd) = store.createTempDirInStore();
+            delTempDir.emplace(tempDir);
+
+            auto tmpOutput = tempDir / "x";
+
+            /* Serialise and create a fresh copy of the output to break
+               any stale writable file descriptors. Copy through the
+               serialisation/deserialisation. TODO: Use copyRecursive here and
+               make use of reflinking. */
+            auto source = sinkToSource([&](Sink & nextSink) { dumpPath(actualPath, nextSink); });
+            restorePath(tmpOutput, *source, settings.fsyncStorePaths);
+            /* This makes it slightly harder to make sense of the control flow. The rule
+               of thumb is that actualPath points to the current location of the stuff
+               that we'll end up registering. */
+            actualPath = std::move(tmpOutput);
+        };
+
         ValidPathInfo newInfo = std::visit(
             overloaded{
 
@@ -1634,14 +1660,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
 
                 [&](const DerivationOutput::CAFixed & dof) {
                     auto & wanted = dof.ca.hash;
-
-                    // Replace the output by a fresh copy of itself to make sure
-                    // that there's no stale file descriptor pointing to it
-                    Path tmpOutput = actualPath + ".tmp";
-                    copyFile(std::filesystem::path(actualPath), std::filesystem::path(tmpOutput), true);
-
-                    std::filesystem::rename(tmpOutput, actualPath);
-
+                    moveOutputToTempDir();
                     auto newInfo0 = newInfoFromCA(
                         DerivationOutput::CAFloating{
                             .method = dof.ca.method,
@@ -1682,6 +1701,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
                 },
 
                 [&](const DerivationOutput::Impure & doi) {
+                    moveOutputToTempDir();
                     return newInfoFromCA(
                         DerivationOutput::CAFloating{
                             .method = doi.method,
+126 −0
Original line number Diff line number Diff line
From dfda33d358f4522450737c40407ba8d8403a86a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= <joerg@thalheim.io>
Date: Mon, 6 Apr 2026 16:49:13 +0200
Subject: [PATCH] Fixes for GHSA-g3g9-5vj6-r3gj
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Squashed commit of the following:

commit d1f2de7d683bef1e2a164f23f33172c79c7725ea
Author: Sergei Zimmerman <sergei@zimmerman.foo>
Date:   Fri Apr 3 00:21:31 2026 +0300

    derivation-builder: Don't use copyFile for FOD output copying, put the output in a temporary directory in the store

commit 2f13ee6dc4ade1d919c14757891d5d8a1001eee8
Author: Sergei Zimmerman <sergei@zimmerman.foo>
Date:   Fri Apr 3 00:21:21 2026 +0300

    libstore: Make temporary in-store directory not world-readable

Signed-off-by: Jörg Thalheim <joerg@thalheim.io>
---
 src/libstore/include/nix/store/local-store.hh |  2 ++
 src/libstore/local-store.cc                   |  5 +--
 src/libstore/unix/build/derivation-builder.cc | 36 ++++++++++++++-----
 3 files changed, 33 insertions(+), 10 deletions(-)

diff --git a/src/libstore/include/nix/store/local-store.hh b/src/libstore/include/nix/store/local-store.hh
index 512c198b0..0b1e13b51 100644
--- a/src/libstore/include/nix/store/local-store.hh
+++ b/src/libstore/include/nix/store/local-store.hh
@@ -505,6 +505,8 @@ private:
 
     friend struct PathSubstitutionGoal;
     friend struct DerivationGoal;
+    /* Only used for createTempDirInStore. */
+    friend class DerivationBuilderImpl;
 };
 
 } // namespace nix
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 649ad70dd..686e9988e 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -1283,8 +1283,9 @@ std::pair<std::filesystem::path, AutoCloseFD> LocalStore::createTempDirInStore()
     do {
         /* There is a slight possibility that `tmpDir' gets deleted by
            the GC between createTempDir() and when we acquire a lock on it.
-           We'll repeat until 'tmpDir' exists and we've locked it. */
-        tmpDirFn = createTempDir(std::filesystem::path{config->realStoreDir.get()}, "tmp");
+           We'll repeat until 'tmpDir' exists and we've locked it.
+           Make the directory accessible only to the current user. */
+        tmpDirFn = createTempDir(std::filesystem::path{config->realStoreDir.get()}, "tmp", /*mode=*/0700);
         tmpDirFd = openDirectory(tmpDirFn);
         if (!tmpDirFd) {
             continue;
diff --git a/src/libstore/unix/build/derivation-builder.cc b/src/libstore/unix/build/derivation-builder.cc
index da225dc0d..fc877dd56 100644
--- a/src/libstore/unix/build/derivation-builder.cc
+++ b/src/libstore/unix/build/derivation-builder.cc
@@ -1595,6 +1595,13 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
         assert(output && scratchPath);
         auto actualPath = realPathInHost(store.printStorePath(*scratchPath));
 
+        /* An optional file descriptor of a directory used for intermediate
+           operations. */
+        AutoCloseFD tempDirFd;
+        /* RAII cleanup of a temporary directory inside the store that is used
+           for intermediate operations. */
+        AutoDelete delTempDir;
+
         auto finish = [&](StorePath finalStorePath) {
             /* Store the final path */
             finalOutputs.insert_or_assign(outputName, finalStorePath);
@@ -1742,6 +1749,25 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
             return newInfo0;
         };
 
+        auto moveOutputToTempDir = [&]() -> void {
+            std::filesystem::path tempDir;
+            std::tie(tempDir, tempDirFd) = store.createTempDirInStore();
+            delTempDir = AutoDelete(tempDir);
+
+            auto tmpOutput = tempDir / "x";
+
+            /* Serialise and create a fresh copy of the output to break
+               any stale writable file descriptors. Copy through the
+               serialisation/deserialisation. TODO: Use copyRecursive here and
+               make use of reflinking. */
+            auto source = sinkToSource([&](Sink & nextSink) { dumpPath(actualPath, nextSink); });
+            restorePath(tmpOutput, *source, store.config->getLocalSettings().fsyncStorePaths);
+            /* This makes it slightly harder to make sense of the control flow. The rule
+               of thumb is that actualPath points to the current location of the stuff
+               that we'll end up registering. */
+            actualPath = std::move(tmpOutput);
+        };
+
         ValidPathInfo newInfo = std::visit(
             overloaded{
 
@@ -1769,14 +1795,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
 
                 [&](const DerivationOutput::CAFixed & dof) {
                     auto & wanted = dof.ca.hash;
-
-                    // Replace the output by a fresh copy of itself to make sure
-                    // that there's no stale file descriptor pointing to it
-                    std::filesystem::path tmpOutput = actualPath.native() + ".tmp";
-                    copyFile(actualPath, tmpOutput, true);
-
-                    std::filesystem::rename(tmpOutput, actualPath);
-
+                    moveOutputToTempDir();
                     return newInfoFromCA(
                         DerivationOutput::CAFloating{
                             .method = dof.ca.method,
@@ -1793,6 +1812,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
                 },
 
                 [&](const DerivationOutput::Impure & doi) {
+                    moveOutputToTempDir();
                     return newInfoFromCA(
                         DerivationOutput::CAFloating{
                             .method = doi.method,
Loading