Nix: bump to latest stable releases (#507699)
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
{
|
||||
x86_64-linux = "/nix/store/mxn3vxpvk2b42kgd08aw4bn27qhf434w-nix-2.31.3";
|
||||
i686-linux = "/nix/store/b7d35ifww9683l74ydkb0qhrq6lqcisi-nix-2.31.3";
|
||||
aarch64-linux = "/nix/store/ysz7dwmy4zd1zm3wzx5dh9g999xv7pbm-nix-2.31.3";
|
||||
riscv64-linux = "/nix/store/avfv2lqbnphj2ap8y5ihg1l0sqhpjll7-nix-riscv64-unknown-linux-gnu-2.31.3";
|
||||
x86_64-darwin = "/nix/store/s8lcl6nrd5ia7nr4zx9mg720i9f8qm37-nix-2.31.3";
|
||||
aarch64-darwin = "/nix/store/vfjzbcl3kf9jjwh0g2x03cvz2x5hg8py-nix-2.31.3";
|
||||
x86_64-linux = "/nix/store/vals1fs2rl6yn5f8gbqj9mvly4r27shs-nix-2.31.4";
|
||||
i686-linux = "/nix/store/fyrlz8cdzvf5csdh5885wifpxc8ywdii-nix-2.31.4";
|
||||
aarch64-linux = "/nix/store/19p3nc892m7idfg2ngd1614660xqbhnm-nix-2.31.4";
|
||||
riscv64-linux = "/nix/store/x1isvq0xnyrg0l29qk2xlp929cgjsmqy-nix-riscv64-unknown-linux-gnu-2.31.4";
|
||||
x86_64-darwin = "/nix/store/4gqxzd5zkxcq271wi5saml4zd92rdkws-nix-2.31.4";
|
||||
aarch64-darwin = "/nix/store/r3gz609kdqchxcmil7dhbravbq8kwm93-nix-2.31.4";
|
||||
}
|
||||
|
||||
@@ -155,108 +155,75 @@ lib.makeExtensible (
|
||||
(
|
||||
{
|
||||
nix_2_28 = commonMeson {
|
||||
version = "2.28.5";
|
||||
hash = "sha256-oIfAHxO+BCtHXJXLHBnsKkGl1Pw+Uuq1PwNxl+lZ+Oc=";
|
||||
version = "2.28.6";
|
||||
hash = "sha256-jg2YDTFt8CY4kMg4ha3UK5C+mQY+Zg67nwNy+CmTk5w=";
|
||||
self_attribute_name = "nix_2_28";
|
||||
patches = patches_common ++ [
|
||||
(fetchpatch2 {
|
||||
name = "nix-2.28-14764-mdbook-0.5-support.patch";
|
||||
url = "https://github.com/NixOS/nix/commit/5a64138e862fe364e751c5c286e8db8c466aaee7.patch?full_index=1";
|
||||
hash = "sha256-vFv/D08x9urtoIE9wiC7Lln4Eq3sgNBwU7TBE1iyrfI=";
|
||||
})
|
||||
lowdown30PatchOld
|
||||
./patches/ghsa-g3g9-5vj6-r3gj-2.28.patch
|
||||
];
|
||||
};
|
||||
|
||||
nixComponents_2_30 =
|
||||
(nixDependencies.callPackage ./modular/packages.nix rec {
|
||||
version = "2.30.3";
|
||||
version = "2.30.4";
|
||||
inherit teams;
|
||||
otherSplices = generateSplicesForNixComponents "nixComponents_2_30";
|
||||
src = fetchFromGitHub {
|
||||
owner = "NixOS";
|
||||
repo = "nix";
|
||||
tag = version;
|
||||
hash = "sha256-kBuwzMgIE9Tmve0Rpp+q+YCsE2mw9d62M/950ViWeJ0=";
|
||||
hash = "sha256-cJ96IBZCYoX0Tdlo5Q7qDSAKfL6QcUq/4Kr1UplH50E=";
|
||||
};
|
||||
}).appendPatches
|
||||
(
|
||||
patches_common
|
||||
++ [
|
||||
(fetchpatch2 {
|
||||
name = "nix-2.30-14695-mdbook-0.5-support.patch";
|
||||
url = "https://github.com/NixOS/nix/commit/5cbd7856de0a9c13351f98e32a1e26d0854d87fd.patch?full_index=1";
|
||||
hash = "sha256-r2ZF1zBZDKMvyX6X4VsaTMrg0zdjn59Jf6Hqg56r29E=";
|
||||
})
|
||||
lowdown30PatchOld
|
||||
./patches/ghsa-g3g9-5vj6-r3gj-2.30.patch
|
||||
]
|
||||
);
|
||||
(patches_common ++ [ lowdown30PatchOld ]);
|
||||
|
||||
nix_2_30 = addTests "nix_2_30" self.nixComponents_2_30.nix-everything;
|
||||
|
||||
nixComponents_2_31 =
|
||||
(nixDependencies.callPackage ./modular/packages.nix rec {
|
||||
version = "2.31.3";
|
||||
version = "2.31.4";
|
||||
inherit teams;
|
||||
otherSplices = generateSplicesForNixComponents "nixComponents_2_31";
|
||||
src = fetchFromGitHub {
|
||||
owner = "NixOS";
|
||||
repo = "nix";
|
||||
tag = version;
|
||||
hash = "sha256-oe0YWe8f+pwQH4aYD2XXLW5iEHyXNUddurqJ5CUVCIk=";
|
||||
hash = "sha256-f/haYfcI+9IiYVH+g6cjhF8cK7QWHAFfcPtF+57ujZ0=";
|
||||
};
|
||||
}).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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
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,
|
||||
@@ -1,126 +0,0 @@
|
||||
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,
|
||||
@@ -1,126 +0,0 @@
|
||||
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,
|
||||
@@ -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,
|
||||
@@ -1,126 +0,0 @@
|
||||
From 0af4c1f88d0646bda0a90a37105360cab466a550 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 a760af86b3a42aa5ac9d9002929107fe357bf128
|
||||
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 0e3412a93f43a017342c267000c152e5c45327e7
|
||||
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 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<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, 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,
|
||||
-232
@@ -1,232 +0,0 @@
|
||||
From b743f908b10aa56b88fb490de8a0d893bd2d83d5 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 a275f4edc..dc8a7a940 100644
|
||||
--- a/src/libstore/meson.build
|
||||
+++ b/src/libstore/meson.build
|
||||
@@ -77,6 +77,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 eaac2bc28..dc5abf808 100644
|
||||
--- a/src/libstore/unix/build/linux-derivation-builder.cc
|
||||
+++ b/src/libstore/unix/build/linux-derivation-builder.cc
|
||||
@@ -1,10 +1,16 @@
|
||||
#ifdef __linux__
|
||||
|
||||
+# include "store-config-private.hh"
|
||||
+
|
||||
# include "nix/store/personality.hh"
|
||||
# include "nix/util/cgroup.hh"
|
||||
# include "nix/util/linux-namespaces.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>
|
||||
@@ -13,11 +19,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 {
|
||||
@@ -121,6 +132,77 @@ static void setupSeccomp()
|
||||
# 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 Path & source, const Path & target, bool optional = false)
|
||||
{
|
||||
debug("bind mounting '%1%' to '%2%'", source, target);
|
||||
@@ -159,8 +241,27 @@ struct LinuxDerivationBuilder : virtual DerivationBuilderImpl
|
||||
|
||||
void enterChroot() override
|
||||
{
|
||||
+ /* 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 (!settings.allowNewPrivileges)
|
||||
+ if (::prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
|
||||
+ throw SysError("failed to set PR_SET_NO_NEW_PRIVS");
|
||||
+
|
||||
setupSeccomp();
|
||||
|
||||
+# 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(drv.platform);
|
||||
}
|
||||
};
|
||||
@@ -709,4 +810,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}")
|
||||
-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}")
|
||||
-231
@@ -1,231 +0,0 @@
|
||||
From 44017ca497c8b44d5dac179f5afc63e91fe45ed6 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
|
||||
|
||||
Signed-off-by: Jörg Thalheim <joerg@thalheim.io>
|
||||
---
|
||||
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 <algorithm>
|
||||
# include <string_view>
|
||||
+# include <cstdint>
|
||||
+
|
||||
# include <sys/ioctl.h>
|
||||
# include <net/if.h>
|
||||
# include <netinet/ip.h>
|
||||
@@ -19,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 {
|
||||
@@ -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}")
|
||||
Reference in New Issue
Block a user