nixVersions.nix_2_34: 2.34.4 -> 2.34.5
The 2.34.5 tarball already contains the GHSA-g3g9-5vj6-r3gj sandbox escape fix and the landlock abstract socket hardening, so drop the now-redundant downstream backports.
This commit is contained in:
@@ -203,23 +203,17 @@ lib.makeExtensible (
|
||||
|
||||
nixComponents_2_34 =
|
||||
(nixDependencies.callPackage ./modular/packages.nix rec {
|
||||
version = "2.34.4";
|
||||
version = "2.34.5";
|
||||
inherit teams;
|
||||
otherSplices = generateSplicesForNixComponents "nixComponents_2_34";
|
||||
src = fetchFromGitHub {
|
||||
owner = "NixOS";
|
||||
repo = "nix";
|
||||
tag = version;
|
||||
hash = "sha256-WPuGqMQGepXoRYjtRudMAMHEoLsIObw2x4sVfho5feA=";
|
||||
hash = "sha256-/S2bnz+TbRFGmNyR31Hfa70uFvJoMM9wYDjpyEw8I+U=";
|
||||
};
|
||||
}).appendPatches
|
||||
(
|
||||
patches_common
|
||||
++ [
|
||||
./patches/ghsa-g3g9-5vj6-r3gj-2.34.patch
|
||||
./patches/landlock-abstract-socket-hardening-2.34.patch
|
||||
]
|
||||
);
|
||||
patches_common;
|
||||
|
||||
nix_2_34 = addTests "nix_2_34" self.nixComponents_2_34.nix-everything;
|
||||
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
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,
|
||||
-234
@@ -1,234 +0,0 @@
|
||||
From 23c7a6490a829a978af1acd56d76cc2dee7f4ad6 Mon Sep 17 00:00:00 2001
|
||||
From: Sergei Zimmerman <sergei@zimmerman.foo>
|
||||
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
|
||||
|
||||
(cherry picked from commit 44017ca497c8b44d5dac179f5afc63e91fe45ed6)
|
||||
Signed-off-by: Jörg Thalheim <joerg@thalheim.io>
|
||||
---
|
||||
src/libstore/meson.build | 5 +
|
||||
.../unix/build/linux-derivation-builder.cc | 103 ++++++++++++++++++
|
||||
tests/nixos/ca-fd-leak/default.nix | 8 +-
|
||||
3 files changed, 114 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/libstore/meson.build b/src/libstore/meson.build
|
||||
index 9e52517d4..edc476ae7 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 555a3b6da..476baabe2 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"
|
||||
@@ -9,6 +11,10 @@
|
||||
# include "nix/util/serialise.hh"
|
||||
# include "linux/fchmodat2-compat.hh"
|
||||
|
||||
+# include <algorithm>
|
||||
+# include <string_view>
|
||||
+# include <cstdint>
|
||||
+
|
||||
# include <sys/ioctl.h>
|
||||
# include <net/if.h>
|
||||
# include <netinet/ip.h>
|
||||
@@ -17,11 +23,16 @@
|
||||
# include <sys/param.h>
|
||||
# include <sys/mount.h>
|
||||
# include <sys/syscall.h>
|
||||
+# include <sys/prctl.h>
|
||||
|
||||
# if HAVE_SECCOMP
|
||||
# include <seccomp.h>
|
||||
# endif
|
||||
|
||||
+# if HAVE_LANDLOCK
|
||||
+# include <linux/landlock.h>
|
||||
+# endif
|
||||
+
|
||||
# define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old))
|
||||
|
||||
namespace nix {
|
||||
@@ -125,6 +136,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));
|
||||
@@ -165,8 +247,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,
|
||||
@@ -760,4 +861,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}")
|
||||
Reference in New Issue
Block a user