From d22a182395ccfc06d84b1b13a6ea660b0aeddb47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 7 Apr 2026 21:30:07 +0200 Subject: [PATCH] nixVersions.git: 2026-03-28 -> 2026-04-07 Master already contains the GHSA-g3g9-5vj6-r3gj sandbox escape fix and the landlock abstract socket hardening, so drop the now-redundant downstream backports. --- pkgs/tools/package-management/nix/default.nix | 14 +- .../nix/modular/src/nix/package.nix | 3 + .../nix/patches/ghsa-g3g9-5vj6-r3gj-git.patch | 126 ---------- ...ndlock-abstract-socket-hardening-git.patch | 231 ------------------ 4 files changed, 7 insertions(+), 367 deletions(-) delete mode 100644 pkgs/tools/package-management/nix/patches/ghsa-g3g9-5vj6-r3gj-git.patch delete mode 100644 pkgs/tools/package-management/nix/patches/landlock-abstract-socket-hardening-git.patch diff --git a/pkgs/tools/package-management/nix/default.nix b/pkgs/tools/package-management/nix/default.nix index 8da4c6b494fb..0be4bdb9205a 100644 --- a/pkgs/tools/package-management/nix/default.nix +++ b/pkgs/tools/package-management/nix/default.nix @@ -213,23 +213,17 @@ lib.makeExtensible ( nixComponents_git = (nixDependencies.callPackage ./modular/packages.nix rec { - version = "2.35pre20260328_${lib.substring 0 8 src.rev}"; + version = "2.35pre20260407_${lib.substring 0 8 src.rev}"; inherit teams; otherSplices = generateSplicesForNixComponents "nixComponents_git"; src = fetchFromGitHub { owner = "NixOS"; repo = "nix"; - rev = "7edcd0a24dc71abb7caa600527833ef540c1bc86"; - hash = "sha256-fybp46IQmRN7lEUTChc3MTqxmRutmDO4RNSPEQfJQsQ="; + rev = "a37db9d249afd61a81ae26368696f60e065d6f61"; + hash = "sha256-RpfExg4DcWZ/SanVuwVbdijqPylsjvtMrHTQHemE+t8="; }; }).appendPatches - ( - patches_common - ++ [ - ./patches/ghsa-g3g9-5vj6-r3gj-git.patch - ./patches/landlock-abstract-socket-hardening-git.patch - ] - ); + patches_common; git = addTests "git" self.nixComponents_git.nix-everything; diff --git a/pkgs/tools/package-management/nix/modular/src/nix/package.nix b/pkgs/tools/package-management/nix/modular/src/nix/package.nix index d96823a991c7..deb49c9e7b4d 100644 --- a/pkgs/tools/package-management/nix/modular/src/nix/package.nix +++ b/pkgs/tools/package-management/nix/modular/src/nix/package.nix @@ -7,6 +7,8 @@ nix-main, nix-cmd, + mimalloc, + # Configuration Options version, @@ -23,6 +25,7 @@ mkMesonExecutable (finalAttrs: { nix-expr nix-main nix-cmd + mimalloc ]; mesonFlags = [ diff --git a/pkgs/tools/package-management/nix/patches/ghsa-g3g9-5vj6-r3gj-git.patch b/pkgs/tools/package-management/nix/patches/ghsa-g3g9-5vj6-r3gj-git.patch deleted file mode 100644 index 05fb52902018..000000000000 --- a/pkgs/tools/package-management/nix/patches/ghsa-g3g9-5vj6-r3gj-git.patch +++ /dev/null @@ -1,126 +0,0 @@ -From 0af4c1f88d0646bda0a90a37105360cab466a550 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= -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 a760af86b3a42aa5ac9d9002929107fe357bf128 -Author: Sergei Zimmerman -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 0e3412a93f43a017342c267000c152e5c45327e7 -Author: Sergei Zimmerman -Date: Fri Apr 3 00:21:21 2026 +0300 - - libstore: Make temporary in-store directory not world-readable - -Signed-off-by: Jörg Thalheim ---- - 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 63a1da67d..bf3437e95 100644 ---- a/src/libstore/include/nix/store/local-store.hh -+++ b/src/libstore/include/nix/store/local-store.hh -@@ -512,6 +512,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 e9eb48bfe..d5e457731 100644 ---- a/src/libstore/local-store.cc -+++ b/src/libstore/local-store.cc -@@ -1304,8 +1304,9 @@ std::pair 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, FinalSymlink::DontFollow); - if (!tmpDirFd) { - continue; -diff --git a/src/libstore/unix/build/derivation-builder.cc b/src/libstore/unix/build/derivation-builder.cc -index 8f6343e0f..8288a4a31 100644 ---- a/src/libstore/unix/build/derivation-builder.cc -+++ b/src/libstore/unix/build/derivation-builder.cc -@@ -1597,6 +1597,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); -@@ -1744,6 +1751,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{ - -@@ -1771,14 +1797,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, -@@ -1795,6 +1814,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs() - }, - - [&](const DerivationOutput::Impure & doi) { -+ moveOutputToTempDir(); - return newInfoFromCA( - DerivationOutput::CAFloating{ - .method = doi.method, diff --git a/pkgs/tools/package-management/nix/patches/landlock-abstract-socket-hardening-git.patch b/pkgs/tools/package-management/nix/patches/landlock-abstract-socket-hardening-git.patch deleted file mode 100644 index 5b0fbdb46536..000000000000 --- a/pkgs/tools/package-management/nix/patches/landlock-abstract-socket-hardening-git.patch +++ /dev/null @@ -1,231 +0,0 @@ -From 44017ca497c8b44d5dac179f5afc63e91fe45ed6 Mon Sep 17 00:00:00 2001 -From: Sergei Zimmerman -Date: Sun, 5 Apr 2026 16:39:58 +0300 -Subject: [PATCH] libstore: Use landlock with - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET for new enough kernels -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -This partially fixes the issue with cooperating processes being able -to communicate via abstract sockets. The fix is partial, because processes -outside the landlock domain of the sandboxed process can still connect to -a socket created by the FOD. There's no equivalent way of restricting inbound -connections. This closes the gap when there's no cooperating process on the host -(i.e. 2 separate FODs). - ->= 6.12 kernel is widespread enough (NixOS 25.11 ships it by -default) that we have no reason not to apply this hardening, even though -it's incomplete. - -ca-fd-leak test exercises this exact code path and now the smuggling -process fails with (on new enough kernels that have landlock support enabled): - -vm-test-run-ca-fd-leak> machine # sandbox setup: applied landlock sandboxing -vm-test-run-ca-fd-leak> machine # building '/nix/store/s7brgi6pdr5f3n8yqlgmdlz8blb89njc-smuggled.drv'... -vm-test-run-ca-fd-leak> machine # building derivation '/nix/store/s7brgi6pdr5f3n8yqlgmdlz8blb89njc-smuggled.drv': woken up -vm-test-run-ca-fd-leak> machine # connect: Operation not permitted -vm-test-run-ca-fd-leak> machine # sendmsg: Socket not connected - -Signed-off-by: Jörg Thalheim ---- - src/libstore/meson.build | 5 + - .../unix/build/linux-derivation-builder.cc | 101 ++++++++++++++++++ - tests/nixos/ca-fd-leak/default.nix | 8 +- - 3 files changed, 112 insertions(+), 2 deletions(-) - -diff --git a/src/libstore/meson.build b/src/libstore/meson.build -index 445798544..753c78687 100644 ---- a/src/libstore/meson.build -+++ b/src/libstore/meson.build -@@ -79,6 +79,11 @@ foreach funcspec : check_funcs - configdata_priv.set(define_name, define_value) - endforeach - -+if host_machine.system() == 'linux' -+ has_landlock = cxx.has_header('linux/landlock.h') -+ configdata_priv.set('HAVE_LANDLOCK', has_landlock.to_int()) -+endif -+ - has_acl_support = cxx.has_header('sys/xattr.h') \ - and cxx.has_function('llistxattr') \ - and cxx.has_function('lremovexattr') -diff --git a/src/libstore/unix/build/linux-derivation-builder.cc b/src/libstore/unix/build/linux-derivation-builder.cc -index 9cfd3cbcd..c71d23e15 100644 ---- a/src/libstore/unix/build/linux-derivation-builder.cc -+++ b/src/libstore/unix/build/linux-derivation-builder.cc -@@ -1,5 +1,7 @@ - #ifdef __linux__ - -+# include "store-config-private.hh" -+ - # include "nix/store/globals.hh" - # include "nix/store/personality.hh" - # include "nix/store/filetransfer.hh" -@@ -11,6 +13,8 @@ - - # include - # include -+# include -+ - # include - # include - # include -@@ -19,11 +23,16 @@ - # include - # include - # include -+# include - - # if HAVE_SECCOMP - # include - # endif - -+# if HAVE_LANDLOCK -+# include -+# endif -+ - # define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old)) - - namespace nix { -@@ -129,6 +138,77 @@ static void setupSeccomp(const LocalSettings & localSettings) - # endif - } - -+# if HAVE_LANDLOCK && defined(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET) -+ -+# define DO_LANDLOCK 1 -+ -+/* We are using LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET on best-effort basis. There are no glibc wrappers for now. */ -+ -+static int landlockCreateRuleset(const ::landlock_ruleset_attr * attr, std::size_t size, std::uint32_t flags) -+{ -+ return ::syscall(__NR_landlock_create_ruleset, attr, size, flags); -+} -+ -+static int landlockRestrictSelf(Descriptor rulesetFd, std::uint32_t flags) -+{ -+ return ::syscall(__NR_landlock_restrict_self, rulesetFd, flags); -+} -+ -+static int getLandlockAbiVersion() -+{ -+ int abiVersion = landlockCreateRuleset(nullptr, 0, LANDLOCK_CREATE_RULESET_VERSION); -+ return abiVersion; -+} -+ -+static void setupLandlock() -+{ -+ bool landlockSupportsScopeAbstractUnixSocket = []() { -+ int abiVersion = getLandlockAbiVersion(); -+ if (abiVersion >= 6) -+ /* All good, we can use LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET. See -+ https://docs.kernel.org/userspace-api/landlock.html#abstract-unix-socket-abi-6 */ -+ return true; -+ -+ if (abiVersion == -1) { -+ debug("landlock is not available"); -+ return false; -+ } -+ -+ debug("landlock version %d does not support LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", abiVersion); -+ return false; -+ }(); -+ -+ /* Bail out early if landlock is not enabled or LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET wouldn't work. -+ TODO: Consider adding more landlock rules for filesystem access as defense-in-depth on top. */ -+ if (!landlockSupportsScopeAbstractUnixSocket) -+ return; -+ -+ ::landlock_ruleset_attr attr = { -+ /* This prevents multiple FODs from communicating with each other -+ via abstract sockets. Note that cooperating processes outside the -+ sandbox can still connect to an abstract socket created by the FOD. To -+ mitigate that issue entirely we'd still need network namespaces. */ -+ .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET, -+ }; -+ -+ /* This better not fail - if the kernel reports a new enough ABI version we -+ should treat any errors as fatal from now on. */ -+ AutoCloseFD rulesetFd = landlockCreateRuleset(&attr, sizeof(attr), 0); -+ if (!rulesetFd) -+ throw SysError("failed to create a landlock ruleset"); -+ -+ if (landlockRestrictSelf(rulesetFd.get(), 0) == -1) -+ throw SysError("failed to apply landlock"); -+ -+ debug("applied landlock sandboxing"); -+} -+ -+# else -+ -+# define DO_LANDLOCK 0 -+ -+# endif -+ - static void doBind(const std::filesystem::path & source, const std::filesystem::path & target, bool optional = false) - { - debug("bind mounting %1% to %2%", PathFmt(source), PathFmt(target)); -@@ -169,8 +249,27 @@ struct LinuxDerivationBuilder : virtual DerivationBuilderImpl - { - auto & localSettings = store.config->getLocalSettings(); - -+ /* Set the NO_NEW_PRIVS before doing seccomp/landlock setup. -+ landlock_restrict_self requires either NO_NEW_PRIVS or CAP_SYS_ADMIN. -+ With user namespaces we do get CAP_SYS_ADMIN. */ -+ if (!localSettings.allowNewPrivileges) -+ if (::prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) -+ throw SysError("failed to set PR_SET_NO_NEW_PRIVS"); -+ - setupSeccomp(localSettings); - -+# if DO_LANDLOCK -+ try { -+ setupLandlock(); -+ } catch (SysError & e) { -+ if (e.errNo != EPERM) -+ throw; -+ /* If allowNewPrivileges is true and we don't have CAP_SYS_ADMIN -+ this code path might be hit. */ -+ warn("setting up landlock: %s", e.message()); -+ } -+# endif -+ - linux::setPersonality({ - .system = drv.platform, - .impersonateLinux26 = localSettings.impersonateLinux26, -@@ -765,4 +864,6 @@ struct ChrootLinuxDerivationBuilder : ChrootDerivationBuilder, LinuxDerivationBu - - } // namespace nix - -+# undef DO_LANDLOCK -+ - #endif -diff --git a/tests/nixos/ca-fd-leak/default.nix b/tests/nixos/ca-fd-leak/default.nix -index 902aacdc6..dc944290f 100644 ---- a/tests/nixos/ca-fd-leak/default.nix -+++ b/tests/nixos/ca-fd-leak/default.nix -@@ -78,7 +78,7 @@ in - - # Build the smuggled derivation. - # This will connect to the smuggler server and send it the file descriptor -- machine.succeed(r""" -+ sender_output = machine.succeed(r""" - nix-build -E ' - builtins.derivation { - name = "smuggled"; -@@ -89,9 +89,13 @@ in - outputHash = builtins.hashString "sha256" "hello, world\n"; - builder = "${pkgs.busybox-sandbox-shell}/bin/sh"; - args = [ "-c" "echo \"hello, world\" > $out; ''${${sender}} ${socketName}" ]; -- }' -+ }' 2>&1 - """.strip()) - -+ # Landlock's LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET prevents a sandboxed process -+ # from connecting to an abstract socket created in an unrelated landlock domain. -+ # There's no such flag for preventing inbound connections. -+ assert "connect: Operation not permitted" in sender_output - - # Tell the smuggler server that we're done - machine.execute("echo done | ${pkgs.socat}/bin/socat - ABSTRACT-CONNECT:${socketName}")