Compare commits

...
21 Commits
Author SHA1 Message Date
Raito Bezarius 60f41e991d release: 2.93.1 "Bici Bici"
Release produced with releng/create_release.xsh

Change-Id: I64c89d0fba1e228136e50738e7a61d53306d14e6
2025-06-23 23:41:40 +02:00
Raito Bezarius 741a5ca774 release: release notes for 2.93.1
Release created with releng/create_release.xsh

Change-Id: I2d80bc68b7dd184ccf449de747ac46de6ac8786a
2025-06-23 23:40:50 +02:00
Raito Bezarius 2f75369094 version: 2.93.0 -> 2.93.1
* Announce the deprecation of ca-derivations and various other features
as planned initially.
* Fixes papercuts in 2.93.0 (SSH connections).
* Fixes the curl download bug for non-Nixpkgs users.
* Fixes CVE-2025-46415, CVE-2025-46416, CVE-2025-52991, CVE-2025-52992,
and CVE-2025-52993.

Change-Id: I8f700396a5ac57d2a1832833f83c22645c73697d
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 23:40:37 +02:00
Raito Bezariusandeldritch horrors 60d50ea31b libstore/build: automatic clean up of unsuccessfully built scratch outputs
When a build fails, its scratch output paths are not cleaned up.

Until recently, this was deemed not a problem but as part of the effort
to harden the Nix builds and protect these paths against being part of a
staged attack (race conditions, etc.), we automatically cleanup after
failed builds.

Change-Id: I58481b1cc83826298b9d80d37fecf81f117ccb09
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 20:49:17 +00:00
eldritch horrors 4f0c59b307 libstore: don't default build-dir to temp-dir
if a build directory is accessible to other users it is possible to
smuggle data in and out of build directories. usually this ins only
a build purity problem, but in combination with other issues it can
be used to break out of a build sandbox. to prevent this we default
to using a subdirectory of nixStateDir (which is more restrictive).

Change-Id: Iacfc9b50534de158618c815f9fb99d7dae1be4d0
2025-06-23 20:48:56 +00:00
eldritch horrorsandPuck Meerburg 46e2bb4ca6 libstore: use pasta for FODs if available
This allows using a userspace program, pasta, to handle comms between
the build sandbox, and the outside world; allowing for full isolation
including the network namespace, closing the "fixed-output derivation
talks to the host over an abstract domain socket" hole for good.

Co-Authored-By: Puck Meerburg <puck@puckipedia.com>
Change-Id: Ifd499b7dbb3784600a6e842fede65fc031ff9f15
2025-06-23 20:48:29 +00:00
eldritch horrorsandRaito Bezarius 201be7976b libutil: add capability support to runProgram2
launching pasta to not run as root will ambient require capabilities.

Change-Id: I1dd2506a1fa3944a9d9062123ef8a74903c597ea
2025-06-23 21:34:35 +02:00
eldritch horrorsandRaito Bezarius 8e4b4c62f5 libutil: add generic redirections runProgram2
explicit stderr redirection makes mergeStderrToStdout unnecessary also.

Change-Id: I63de929e6dc53f6c5ceb2d43c2ce288bfc04d872
2025-06-23 21:34:35 +02:00
eldritch horrorsandRaito Bezarius fe82fcdddc libutil: make RunningProgram more useful
make it moveable, make it killable, and add a stdout fd accessor.

Change-Id: I2387cbe8ac67b899a322cd6c7d306ef9ea7abcd0
2025-06-23 21:34:35 +02:00
Raito Bezarius 3af68dcef0 libutil: ensure that _deletePath does NOT use absolute paths with dirfds
When calling `_deletePath` with a parent file descriptor, `openat` is
made effective by using relative paths to the directory file descriptor.

To avoid the problem, the signature is changed to resist misuse with an
assert in the prologue of the function.

Change-Id: I6b3fc766bad2afe54dc27d47d1df3873e188de96
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 21:34:35 +02:00
Raito Bezarius 0001c1aeaf libstore: ensure that passAsFile is created in the original temp dir
This ensures that `passAsFile` data is created inside the expected
temporary build directory by `openat()` from the parent directory file
descriptor.

This avoids a TOCTOU which is part of the attack chain of CVE-????.

Change-Id: Ie5273446c4a19403088d0389ae8e3f473af8879a
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 17:25:59 +02:00
Raito Bezarius 77daadb029 libutil: writeFile variant for file descriptors
`writeFile` lose its `sync` boolean flag to make things simpler.

A new `writeFileAndSync` function is created and all call sites are
converted to it.

Change-Id: Ib871a5283a9c047db1e4fe48a241506e4aab9192
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 17:25:59 +02:00
Raito Bezarius 3f02ca5c35 libstore: chown to builder variant for file descriptors
We use it immediately for the build temporary directory.

Change-Id: I180193c63a2b98721f5fb8e542c4e39c099bb947
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 16:53:12 +02:00
Raito Bezarius 1a4cb13411 libstore: open build directory as a dirfd as well
We now keep around a proper AutoCloseFD around the temporary directory
which we plan to use for openat operations and avoiding the build
directory being swapped out while we are doing something else.

Change-Id: I18d387b0f123ebf2d20c6405cd47ebadc5505f2a
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 16:53:12 +02:00
Raito Bezarius e9f0354f7a libutil: guess or invent a path from file descriptors
This is useful for certain error recovery paths (no pun intended) that
does not thread through the original path name.

Change-Id: I2d800740cb4f9912e64c923120d3f977c58ccb7e
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-06-23 16:53:12 +02:00
Raito Bezarius fbd6a014ec flake/inputs: 24.11-small -> 25.05-small
We upgrade to 25.05 release, which contains the curl commit
https://github.com/curl/curl/commit/5fbd78eb2dc4afbd8884e8eed27147fc3d4318f6
done in
https://github.com/NixOS/nixpkgs/pull/396200#issuecomment-2795944006.

This fixes HTTP transfers generating arbitrary errors and possibly
failing unusually.

Users who are already depending on 25.05-small or a recent unstable
already had the fix.

Special mention to the Linux kernel who gave me the opportunity to get
on a 24 hours bisection side quest to fix the local release engineering
test.

Special thanks to everyone who had to endure me ranting.

Change-Id: I866caf65d5ea103f1fa5eccd57df8031c9eacda0
Co-authored-by: eldritch horrors <pennae@lix.systems>
Co-authored-by: helle <helle@h3l.li>
Signed-off-by: Raito Bezarius <raito@lix.systems>
(cherry picked from commit 1e34c37477)
2025-06-19 17:09:59 +02:00
Linus Heckemann 2387104452 build: disable LTO on Darwin
Due to https://git.lix.systems/lix-project/lix/issues/832 , Lix 2.93.0
fails to build on Darwin without overrides. Until the root cause has
been determined and fixed, build without LTO.

Change-Id: I4db5eb294d8f19e5a366b1e19efa5a327b3e2e78
(cherry picked from commit da94e860dd)
2025-06-03 19:08:20 +00:00
eldritch horrors d84f13b73f nix: don't send tarballTtl to the daemon
it's an eval-time only setting, the daemon doesn't use it anywhere. this
is a hack, but until we have a much better settings system we are stuck.

fixes #680

Change-Id: I532088b0279f13da0a0a65c2bd2e5f9d1dfb39da
(cherry picked from commit 5917db84aa)
2025-06-03 18:49:43 +00:00
eldritch horrors 37a570bd40 deprecate CA, dynamic, and impure derivations
ca derivations are what we're really after, but dynamic derivations
must also go because they depend on ca derivations. we can't easily
implement dynamic derivations any other way, so we remove them too.
impure derivations build on the content-addressed infrastructure in
ways we cannot easily detangle, so they too must go for time being.

see #815

Change-Id: If61371736dfd89cc71a1b2ae5a005757c3cb9484
(cherry picked from commit d8e2f53d07)
2025-06-03 18:20:54 +00:00
Alois Wohlschlagerandeldritch horrors e62b7236e8 libstore/gc: fix auto-GC blocking indefinitely during evaluation
The first auto-GC request would not be registered as a waiter due to a logic
error. As a result, if that request was synchronous (as happens during
evaluation) it would be stuck forever waiting on a promise that will never be
fulfilled.
Register also the first request properly so that it is notified and unblocked
again when the GC has finished. Also add a test verifying that auto-GC
triggering during evaluation will not get stuck.

Fixes: https://git.lix.systems/lix-project/lix/issues/844
Change-Id: I157afdc737415261e48d6d01d46c586a2927a1ad
(cherry picked from commit 4505bfac8e)
2025-06-03 12:09:32 +00:00
Raito Bezarius 33eaaf02fd libstore/ssh: remove echo started check
The pre-flight `echo started` check over SSH was originally added in
577ebeaefb. As it is usual with these old
commits, understanding why is there a need for something is difficult.

The closest thing would be
> Fix a race starting the SSH master. We now wait synchronously for
> the SSH master to finish starting. This prevents the SSH clients
> from starting their own connections.

But, we removed SSH connection sharing, so this does not apply anymore.

Nonetheless, we believed this check was meant as a way to catch obvious
misconfigurations or SSH failures early, before handing off to
`nix-store`. However, this approach was not fruitful: it assumes the
remote has a `bash`-compatible shell, `echo` behaves in a standard way,
and no `ForceCommand` interferes—all of which are unreliable assumptions
in practice.

While the intent was to provide slightly better diagnostics (e.g. in
case of SSH hanging or returning an interactive shell), in practice it
does not meaningfully catch or improve real failure cases. The
underlying protocol or engine can and should handle those errors more
robustly anyway.

In contrast, this check *does* break several legitimate workflows,
including:

* remote builders using `ForceCommand` wrappers (e.g.
`nix-remote-build`-style setups), see
<https://discourse.nixos.org/t/wrapper-to-restrict-builder-access-through-ssh-worth-upstreaming/25834/15>,

* SSHing into minimal environments lacking `bash` (e.g. initrd,
busybox-based systems),

* configurations that don’t default to POSIX-like shells, e.g., nushell
enthusiasts.

As such, we’re removing this code. Protocol mismatch errors and SSH
failures can be rethought and handled more structurally elsewhere in the
engine.

Change-Id: I187f6881375d42ef83987a13a350c97964bbdb30
Signed-off-by: Raito Bezarius <raito@lix.systems>
(cherry picked from commit 0dd8bf6c1c)
2025-05-18 19:51:41 +00:00
59 changed files with 1022 additions and 433 deletions
+79
View File
@@ -1,4 +1,83 @@
# Lix 2.93 "Bici Bici" (2025-05-09)
# Lix 2.93.1 (2025-06-23)
## Breaking Changes
- Fixed output derivations can be run using `pasta` network isolation [fj#285](https://git.lix.systems/lix-project/lix/issues/285) [cl/3442](https://gerrit.lix.systems/c/lix/+/3442)
Fixed output derivations traditionally run in the host network namespace.
On Linux this allows such derivations to communicate with other sandboxes
or the host using the abstract Unix domains socket namespace; this hasn't
been unproblematic in the past and has been used in two distinct exploits
to break out of the sandbox. For this reason fixed output derivations can
now run in a network namespace (provided by [`pasta`]), restricted to TCP
and UDP communication with the rest of the world. When enabled this could
be a breaking change and we classify it as such, even though we don't yet
enable or require such isolation by default. We may enforce this in later
releases of Lix once we have sufficient confidence that breakage is rare.
[`pasta`]: https://passt.top/
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) and [puck](https://git.lix.systems/puck) for this.
## Fixes
- Always clean up scratch paths after derivations failed to build [cl/3444](https://gerrit.lix.systems/c/lix/+/3444)
Previously, scratch paths created during builds were not always cleaned up if
the derivation failed, potentially leaving behind unnecessary temporary files
or directories in the Nix store.
This fix ensures that such paths are consistently removed after a failed build,
improving Nix store hygiene, hardening Lix against mis-reuse of failed builds
scratch paths.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) for this.
- `build-dir` no longer defaults to `temp-dir` [cl/3443](https://gerrit.lix.systems/c/lix/+/3443)
The directory in which temporary build directories are created no longer defaults
to the value of the `temp-dir` setting to avoid builders making their directories
world-accessible. This behavior has been used to escape the build sandbox and can
cause build impurities even when not used maliciously. We now default to `builds`
in `NIX_STATE_DIR` (which is `/nix/var/nix/builds` in the default configuration).
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- Remove reliance on Bash for remote stores via SSH [fj#830](https://git.lix.systems/lix-project/lix/issues/830) [fj#805](https://git.lix.systems/lix-project/lix/issues/805) [fj#304](https://git.lix.systems/lix-project/lix/issues/304) [cl/3159](https://gerrit.lix.systems/c/lix/+/3159)
The pre-flight `echo started` handshake -- added years ago to catch race conditions -- has been removed.
After removal of connection sharing in Lix 2.93, it required a Bash-compatible shell and a standard `echo`, so it failed on:
* builders protected by `ForceCommand` wrappers (e.g. `nix-remote-build`),
* BusyBox / initrd images with no Bash,
* hosts using non-POSIX shells such as Nushell.
The race the probe once addressed was tied to SSH connection-sharing -- since connection-sharing code has already been removed, the probe is now pointless.
Real connection or protocol errors are now left to SSH/Nix to report directly.
This is technically a breaking change if you had scripts that relied on the literal "started" which needs to be updated to rely on other signals, e.g., exit codes.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
## Miscellany
- Deprecation of CA derivations, dynamic derivations, and impure derivations [fj#815](https://git.lix.systems/lix-project/lix/issues/815)
Content-addressed derivations are now deprecated and slated for removal in Lix 2.94.
We're doing this because the CA derivation system has been a known cause of problems
and inconsistencies, is unmaintained, habitually makes improving the store code very
difficult (or blocks such improvements outright), and is beset by a number of design
flaws that in our opinion cannot be fixed without a full reimplementation from zero.
Dynamic derivations and impure derivations are built on the CA derivation framework,
and owing to this they too are deprecated and slated for removal in another release.
# Lix 2.93.0 (2025-05-09)
+4 -5
View File
@@ -193,13 +193,11 @@ let
in
''
{
${
lib.concatStringsSep "\n" (
${lib.concatStringsSep "\n" (
builtins.map (output: ''
${output} = { outPath = "${lib.getOutput output drv}"; };
'') outputs
)
}
)}
outputs = [ ${lib.concatStringsSep " " (builtins.map (x: "\"${x}\"") outputs)} ];
name = "${drv.name}";
outPath = "${drv}";
@@ -361,7 +359,8 @@ let
"org.opencontainers.image.source" = "https://git.lix.systems/lix-project/lix";
"org.opencontainers.image.vendor" = "Lix project";
"org.opencontainers.image.version" = pkgs.nix.version;
"org.opencontainers.image.description" = "Minimal Lix container image, with some batteries included.";
"org.opencontainers.image.description" =
"Minimal Lix container image, with some batteries included.";
} // lib.optionalAttrs (lixRevision != null) { "org.opencontainers.image.revision" = lixRevision; };
};
Generated
+66 -7
View File
@@ -16,6 +16,22 @@
"type": "github"
}
},
"lowdown-src": {
"flake": false,
"locked": {
"lastModified": 1633514407,
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
"owner": "kristapsdz",
"repo": "lowdown",
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
"type": "github"
},
"original": {
"owner": "kristapsdz",
"repo": "lowdown",
"type": "github"
}
},
"nix2container": {
"flake": false,
"locked": {
@@ -32,18 +48,44 @@
"type": "github"
}
},
"nixpkgs": {
"nix_2_18": {
"inputs": {
"flake-compat": [
"flake-compat"
],
"lowdown-src": "lowdown-src",
"nixpkgs": "nixpkgs",
"nixpkgs-regression": [
"nixpkgs-regression"
]
},
"locked": {
"lastModified": 1733348545,
"narHash": "sha256-b4JrUmqT0vFNx42aEN9LTWOHomkTKL/ayLopflVf81U=",
"lastModified": 1730375271,
"narHash": "sha256-RrOFlDGmRXcVRV2p2HqHGqvzGNyWoD0Dado/BNlJ1SI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9ecb50d2fae8680be74c08bb0a995c5383747f89",
"repo": "nix",
"rev": "0f665ff6779454f2117dcc32e44380cda7f45523",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.11-small",
"ref": "2.18.9",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1705033721,
"narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.05-small",
"repo": "nixpkgs",
"type": "github"
}
@@ -64,6 +106,22 @@
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1749522908,
"narHash": "sha256-eWANkhWXFL1MmaxzsZ9bhLCNT8OVs7CC+OXaSDGlA8A=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e5cb99555c45a13dcc5f1317462238530b0066b7",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.05-small",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"flake": false,
"locked": {
@@ -84,7 +142,8 @@
"inputs": {
"flake-compat": "flake-compat",
"nix2container": "nix2container",
"nixpkgs": "nixpkgs",
"nix_2_18": "nix_2_18",
"nixpkgs": "nixpkgs_2",
"nixpkgs-regression": "nixpkgs-regression",
"pre-commit-hooks": "pre-commit-hooks"
}
+22 -3
View File
@@ -2,8 +2,19 @@
description = "Lix: A modern, delicious implementation of the Nix package manager";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11-small";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05-small";
nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2";
# Required because Nix 2.18 is not in Nixpkgs ≥ 25.05 anymore.
nix_2_18 = {
url = "github:NixOS/nix/2.18.9";
# NOTE(Raito): this is not possible because patches on libseccomp does not apply anymore on this Nix.
# Let's keep the latest known nixpkgs useable with Nix 2.18 for our tests.
# inputs.nixpkgs.follows = "nixpkgs";
inputs.nixpkgs-regression.follows = "nixpkgs-regression";
inputs.flake-compat.follows = "flake-compat";
};
pre-commit-hooks = {
url = "github:cachix/git-hooks.nix";
flake = false;
@@ -25,6 +36,7 @@
nixpkgs-regression,
pre-commit-hooks,
nix2container,
nix_2_18,
flake-compat,
}:
@@ -163,6 +175,11 @@
{
nixStable = prev.nix;
# Nix 2.18 has been removed from Nixpkgs ≥ 25.05, so we need to reintroduce it ourselves for our tests.
nixVersions = prev.nixVersions // {
nix_2_18 = nix_2_18.outputs.packages.${currentStdenv.hostPlatform.system}.default;
};
# Forward from the previous stage as we dont want it to pick the lowdown override
nixUnstable = prev.nixUnstable;
@@ -383,10 +400,12 @@
name = "nixpkgs-lib-tests";
paths =
[ testWithNix ]
# NOTE: nixpkgs 24.11 is being ... *creative*, and requires this dance to override
# NOTE: nixpkgs 25.05 is being ... *creative*, and requires this dance to override
# the evaluator used for the test. it will break again in the future, don't worry.
++ lib.optionals pkgs.stdenv.isLinux [
(pkgs.callPackage "${nixpkgs}/ci/eval" { nixVersions.nix_2_24 = nix; }).attrpathsSuperset
((pkgs.callPackage "${nixpkgs}/ci/eval" { nixVersions.latest = nix; }).attrpathsSuperset {
evalSystem = system;
})
];
}
);
+2 -2
View File
@@ -254,7 +254,7 @@ void runNix(Path program, const Strings & args)
.program = settings.nixBinDir+ "/" + program,
.args = args,
.environment = subprocessEnv,
}).wait();
}).waitAndCheck();
return;
}
@@ -672,7 +672,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
// runProgram redirects stdout to a StringSink,
// using runProgram2 to allow editors to display their UI
runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args }).wait();
runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args }).waitAndCheck();
// Reload right after exiting the editor if path is not in store
// Store is immutable, so there could be no changes, so there's no need to reload
+1 -1
View File
@@ -312,7 +312,7 @@ struct ExprAttrs
AttrDef() { };
template<typename T>
const T & chooseByKind(const T & plain, const T & inherited, const T & inheritedFrom) const
T chooseByKind(const T & plain, const T & inherited, const T & inheritedFrom) const
{
switch (kind) {
case Kind::Plain:
+5 -4
View File
@@ -686,10 +686,12 @@ struct nothing : p::nothing<Rule> {
static_assert(!std::is_base_of_v<semantic, Rule>);
};
template<typename Self, typename OpCtx, typename AttrPathT, typename ExprT>
struct operator_semantics {
struct operator_semantics
{
private:
operator_semantics() = default;
friend Self;
public:
struct has_attr : grammar::v1::op::has_attr {
AttrPathT path;
@@ -775,5 +777,4 @@ public:
return popExpr();
}
};
}
+4 -3
View File
@@ -22,6 +22,7 @@
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std::string_literals;
@@ -168,7 +169,7 @@ WorkdirInfo getWorkdirInfo(const Input & input, const Path & workdir)
.program = "git",
.args = { "-C", workdir, "--git-dir", gitDir, "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" },
.environment = env,
.mergeStderrToStdout = true
.redirections = {{.from = STDERR_FILENO, .to = STDOUT_FILENO}},
});
auto exitCode = WEXITSTATUS(result.first);
auto errorMessage = result.second;
@@ -701,7 +702,7 @@ struct GitInputScheme : InputScheme
auto result = runProgram(RunOptions {
.program = "git",
.args = { "-C", repoDir, "--git-dir", gitDir, "cat-file", "commit", input.getRev()->gitRev() },
.mergeStderrToStdout = true
.redirections = {{.from = STDERR_FILENO, .to = STDOUT_FILENO}},
});
if (WEXITSTATUS(result.first) == 128
&& result.second.find("bad file") != std::string::npos)
@@ -766,7 +767,7 @@ struct GitInputScheme : InputScheme
.args = { "-C", repoDir, "--git-dir", gitDir, "archive", input.getRev()->gitRev() },
.captureStdout = true,
});
Finally const _wait([&] { proc.wait(); });
Finally const _wait([&] { proc.waitAndCheck(); });
unpackTarfile(*proc.getStdout(), tmpDir);
}
+2 -2
View File
@@ -1011,11 +1011,11 @@ void runPostBuildHook(
.program = settings.postBuildHook,
.environment = hookEnvironment,
.captureStdout = true,
.mergeStderrToStdout = true,
.redirections = {{.from = STDERR_FILENO, .to = STDOUT_FILENO}},
});
Finally const _wait([&] {
try {
proc.wait();
proc.waitAndCheck();
} catch (nix::Error & e) {
e.addTrace(nullptr,
"while running the post-build-hook %s for derivation %s",
+96 -12
View File
@@ -13,6 +13,8 @@
#include "lix/libutil/archive.hh"
#include "lix/libstore/daemon.hh"
#include "lix/libutil/regex.hh"
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/topo-sort.hh"
#include "lix/libutil/json.hh"
@@ -25,6 +27,7 @@
#include "lix/libutil/mount.hh"
#include "lix/libutil/strings.hh"
#include "lix/libutil/thread-name.hh"
#include "platform/linux.hh"
#include <cstddef>
#include <exception>
@@ -391,9 +394,13 @@ void LocalDerivationGoal::cleanupPostOutputsRegisteredModeCheck()
void LocalDerivationGoal::cleanupPostOutputsRegisteredModeNonCheck()
{
/* Delete unused redirected outputs (when doing hash rewriting). */
for (auto & i : redirectedOutputs)
deletePath(worker.store.Store::toRealPath(i.second));
/* In the past, redirected outputs were manually tracked for deletion.
* Now that we have the scratch outputs cleaner which are a superset of
* redirected outputs, we just fire all uncancelled automatic deleters now.
*
* This should clean up any paths that IS NOT registered in the database.
*/
scratchOutputsCleaner.clear();
/* Delete the chroot (if we were using one). */
autoDelChroot.reset(); /* this runs the destructor */
@@ -480,17 +487,24 @@ try {
});
}
createDirs(settings.buildDir.get());
/* Create a temporary directory where the build will take
place. */
tmpDir = createTempDir(
settings.buildDir.get().value_or(""),
settings.buildDir.get(),
"nix-build-" + std::string(drvPath.name()),
false,
false,
0700
);
/* The TOCTOU between the previous mkdir call and this open call is unavoidable due to
* POSIX semantics.*/
tmpDirFd = AutoCloseFD{open(tmpDir.c_str(), O_RDONLY | O_NOFOLLOW | O_DIRECTORY)};
if (!tmpDirFd)
throw SysError("failed to open the build temporary directory descriptor '%1%'", tmpDir);
chownToBuilder(tmpDir);
chownToBuilder(tmpDirFd);
for (auto & [outputName, status] : initialOutputs) {
/* Set scratch path we'll actually use during the build.
@@ -521,6 +535,10 @@ try {
to use a temporary path */
makeFallbackPath(status.known->path);
scratchOutputs.insert_or_assign(outputName, scratchPath);
/* Schedule this scratch output path for automatic deletion
* if we do not cancel it, e.g. when registering the outputs.
*/
scratchOutputsCleaner.insert_or_assign(outputName, worker.store.printStorePath(scratchPath));
/* Substitute output placeholders with the scratch output paths.
We'll use during the build. */
@@ -543,8 +561,6 @@ try {
std::string h2 { scratchPath.hashPart() };
inputRewrites[h1] = h2;
}
redirectedOutputs.insert_or_assign(std::move(fixedFinalPath), std::move(scratchPath));
}
/* Construct the environment passed to the builder. */
@@ -858,8 +874,13 @@ void LocalDerivationGoal::initTmpDir() {
auto hash = hashString(HashType::SHA256, i.first);
std::string fn = ".attr-" + hash.to_string(Base::Base32, false);
Path p = tmpDir + "/" + fn;
writeFile(p, rewriteStrings(i.second, inputRewrites));
chownToBuilder(p);
/* TODO(jade): we should have BorrowedFD instead of OwnedFD. */
AutoCloseFD passAsFileFd{openat(tmpDirFd.get(), fn.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC | O_EXCL | O_NOFOLLOW, 0666)};
if (!passAsFileFd) {
throw SysError("opening `passAsFile` file in the sandbox '%1%'", p);
}
writeFile(passAsFileFd, rewriteStrings(i.second, inputRewrites));
chownToBuilder(passAsFileFd);
env[i.first + "Path"] = tmpDirInSandbox + "/" + fn;
}
}
@@ -975,6 +996,13 @@ void LocalDerivationGoal::chownToBuilder(const Path & path)
throw SysError("cannot change ownership of '%1%'", path);
}
void LocalDerivationGoal::chownToBuilder(const AutoCloseFD & fd)
{
if (!buildUser) return;
if (fchown(fd.get(), buildUser->getUID(), buildUser->getGID()) == -1)
throw SysError("cannot change ownership of file '%1%'", fd.guessOrInventPath());
}
void LocalDerivationGoal::runChild()
{
@@ -1105,7 +1133,7 @@ void LocalDerivationGoal::runChild()
/* N.B. it is realistic that these paths might not exist. It
happens when testing Nix building fixed-output derivations
within a pure derivation. */
for (auto & path : { "/etc/resolv.conf", "/etc/services", "/etc/hosts" })
for (auto & path : { "/etc/services", "/etc/hosts" })
if (pathExists(path)) {
// Copy the actual file, not the symlink, because we don't know where
// the symlink is pointing, and we don't want to chase down the entire
@@ -1126,6 +1154,11 @@ void LocalDerivationGoal::runChild()
copyFile(path, chrootRootDir + path, { .followSymlinks = true });
}
if (pathExists("/etc/resolv.conf")) {
const auto resolvConf = rewriteResolvConf(readFile("/etc/resolv.conf"));
writeFile(chrootRootDir + "/etc/resolv.conf", resolvConf);
}
if (settings.caFile != "" && pathExists(settings.caFile)) {
// For the same reasons as above, copy the CA certificates file too.
// It should be even less likely to change during the build than resolv.conf.
@@ -1253,6 +1286,36 @@ void LocalDerivationGoal::runChild()
if (setuid(sandboxUid()) == -1)
throw SysError("setuid failed");
if (runPasta) {
// wait for the pasta interface to appear. pasta can't signal us when
// it's done setting up the namespace, so we have to wait for a while
AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
if (!fd) throw SysError("cannot open IP socket");
struct ifreq ifr;
strcpy(ifr.ifr_name, LinuxLocalDerivationGoal::PASTA_NS_IFNAME);
// wait two minutes for the interface to appear. if it does not do so
// we are either grossly overloaded, or pasta startup failed somehow.
static constexpr int SINGLE_WAIT_US = 1000;
static constexpr int TOTAL_WAIT_US = 120'000'000;
for (unsigned tries = 0; ; tries++) {
if (tries > TOTAL_WAIT_US / SINGLE_WAIT_US) {
throw Error(
"sandbox network setup timed out, please check daemon logs for "
"possible error output."
);
} else if (ioctl(fd.get(), SIOCGIFFLAGS, &ifr) == 0) {
if ((ifr.ifr_ifru.ifru_flags & IFF_UP) != 0) {
break;
}
} else if (errno == ENODEV) {
usleep(SINGLE_WAIT_US);
} else {
throw SysError("cannot get loopback interface flags");
}
}
}
setUser = false;
}
#endif
@@ -1972,7 +2035,9 @@ try {
}
/* Don't register anything, since we already have the
previous versions which we're comparing. */
previous versions which we're comparing.
NOTE: this means that the `.check` path will be automatically deleted.
*/
continue;
}
@@ -1996,8 +2061,13 @@ try {
/* If it's a CA path, register it right away. This is necessary if it
isn't statically known so that we can safely unlock the path before
the next iteration */
if (newInfo.ca)
if (newInfo.ca) {
TRY_AWAIT(localStore.registerValidPaths({{newInfo.path, newInfo}}));
/* Cancel automatic deletion of that output if it was a scratch output. */
if (auto cleaner = scratchOutputsCleaner.extract(outputName)) {
cleaner.mapped().cancel();
}
}
infos.emplace(outputName, std::move(newInfo));
}
@@ -2037,6 +2107,13 @@ try {
infos2.insert_or_assign(newInfo.path, newInfo);
}
TRY_AWAIT(localStore.registerValidPaths(infos2));
/* Cancel automatic deletion of that output if it was a scratch output that we just registered. */
for (auto & [outputName, _ ] : infos) {
if (auto cleaner = scratchOutputsCleaner.extract(outputName)) {
cleaner.mapped().cancel();
}
}
}
/* In case of a fixed-output derivation hash mismatch, throw an
@@ -2070,6 +2147,13 @@ try {
builtOutputs.emplace(outputName, thisRealisation);
}
/* NOTE: At this point, all outputs MAY NOT have been registered.
* Therefore, there may remains auto-deleters pending in the cleaner list (`scratchOutputsCleaner`).
*
* They will be finally deleted but we have no way to assert they all have been, e.g.
* `assert(scratchOutputsCleaner.size() == 0)` cannot be written.
*/
co_return builtOutputs;
} catch (...) {
co_return result::current_exception();
+42 -3
View File
@@ -31,6 +31,11 @@ struct LocalDerivationGoal : public DerivationGoal
*/
Path tmpDir;
/**
* The temporary directory file descriptor
*/
AutoCloseFD tmpDirFd;
/**
* The path of the temporary directory in the sandbox.
*/
@@ -95,8 +100,6 @@ struct LocalDerivationGoal : public DerivationGoal
* Hash rewriting.
*/
StringMap inputRewrites, outputRewrites;
typedef map<StorePath, StorePath> RedirectedOutputs;
RedirectedOutputs redirectedOutputs;
/**
* The outputs paths used during the build.
@@ -113,6 +116,19 @@ struct LocalDerivationGoal : public DerivationGoal
* self-references.
*/
OutputPathMap scratchOutputs;
/**
* Output paths used during the build are scheduled for
* automatic cleanup unless they have been successfully built.
*
* `registerOutputs` take care of cancelling the cleanups
* and clearing this vector.
*
* `startBuilder` take care of filling this vector
* as `scratchOutputs` gets filled.
*
* This is a map from output names to automatic delete handles.
*/
std::map<std::string, AutoDelete> scratchOutputsCleaner;
/**
* Path registration info from the previous round, if we're
@@ -191,10 +207,18 @@ struct LocalDerivationGoal : public DerivationGoal
kj::Promise<Result<void>> writeStructuredAttrs();
/**
* Make a file owned by the builder.
* Make a file owned by the builder addressed by its path.
*
* SAFETY: this function is prone to TOCTOU as it receives a path and not a descriptor.
* It's only safe to call in a child of a directory only visible to the owner.
*/
void chownToBuilder(const Path & path);
/**
* Make a file owned by the builder addressed by its file descriptor.
*/
void chownToBuilder(const AutoCloseFD & fd);
int getChildStatus() override;
/**
@@ -269,6 +293,12 @@ struct LocalDerivationGoal : public DerivationGoal
protected:
using DerivationGoal::DerivationGoal;
/**
* Whether to run pasta for network-endowed derivations. Running pasta
* currently requires actively waiting for its net-ns setup to finish.
*/
bool runPasta = false;
/**
* Setup dependencies outside the sandbox.
* Called in the parent nix process.
@@ -278,6 +308,15 @@ protected:
throw Error("sandboxing builds is not supported on this platform");
};
/**
* Rewrite resolv.conf for use in the sandbox. Used in the linux platform
* to replace nameservers * when using pasta for fixed output derivations.
*/
virtual std::string rewriteResolvConf(std::string fromHost)
{
return fromHost;
}
/**
* Create a new process that runs `openSlave` and `runChild`
* On some platforms this process is created with sandboxing flags.
+9 -2
View File
@@ -45,8 +45,15 @@ std::string SecretKey::signDetached(std::string_view data) const
{
unsigned char sig[crypto_sign_BYTES];
unsigned long long sigLen;
crypto_sign_detached(sig, &sigLen, charptr_cast<const unsigned char *>(data.data()), data.size(),
charptr_cast<const unsigned char *>(key.data()));
crypto_sign_detached(
sig,
&sigLen,
// the following is not a string function so no null termination issues are possible here.
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
charptr_cast<const unsigned char *>(data.data()),
data.size(),
charptr_cast<const unsigned char *>(key.data())
);
return name + ":" + base64Encode(std::string(reinterpret_cast<char *>(sig), sigLen));
}
+1 -1
View File
@@ -966,9 +966,9 @@ try {
{
auto state(_gcState.lock());
state->gcWaiters.push_back(std::move(pfp.fulfiller));
if (state->gcRunning) {
state->gcWaiters.push_back(std::move(pfp.fulfiller));
debug("waiting for auto-GC to finish");
goto sync;
}
+8 -1
View File
@@ -87,6 +87,9 @@ Settings::Settings()
#if defined(__linux__) && defined(SANDBOX_SHELL)
sandboxPaths.setDefault(tokenizeString<StringSet>("/bin/sh=" SANDBOX_SHELL));
#endif
#if defined(__linux__) && defined(PASTA_PATH)
pastaPath.setDefault(PASTA_PATH);
#endif
/* chroot-like behavior from Apple's sandbox */
#if __APPLE__
@@ -243,7 +246,11 @@ StringSet Settings::getDefaultExtraPlatforms()
// x86_64 in aarch64 environments or vice versa since they can
// always exec with their own binary preferences.
if (std::string{SYSTEM} == "aarch64-darwin" &&
runProgram(RunOptions {.program = "arch", .args = {"-arch", "x86_64", "/usr/bin/true"}, .mergeStderrToStdout = true}).first == 0)
runProgram(RunOptions {
.program = "arch",
.args = {"-arch", "x86_64", "/usr/bin/true"},
.redirections = {{.from = STDERR_FILENO, .to = STDOUT_FILENO}}
}).first == 0)
extraPlatforms.insert("x86_64-darwin");
#endif
+3 -3
View File
@@ -173,7 +173,7 @@ static void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd, Ne
txn.commit();
}
writeFile(schemaPath, fmt("%d", nixCASchemaVersion), 0666, true);
writeFileAndSync(schemaPath, fmt("%d", nixCASchemaVersion), 0666);
lockFile(lockFd.get(), ltRead);
}
}
@@ -315,7 +315,7 @@ void LocalStore::initDB(DBState & state)
else if (curSchema == 0) { /* new store */
curSchema = nixSchemaVersion;
openDB(state, true);
writeFile(schemaPath, fmt("%1%", nixSchemaVersion), 0666, true);
writeFileAndSync(schemaPath, fmt("%1%", nixSchemaVersion), 0666);
}
else if (curSchema < nixSchemaVersion) {
@@ -368,7 +368,7 @@ void LocalStore::initDB(DBState & state)
txn.commit();
}
writeFile(schemaPath, fmt("%1%", nixSchemaVersion), 0666, true);
writeFileAndSync(schemaPath, fmt("%1%", nixSchemaVersion), 0666);
lockFile(globalLock.get(), ltRead, always_progresses);
}
+7
View File
@@ -82,6 +82,7 @@ libstore_setting_definitions = files(
'settings/narinfo-cache-negative-ttl.md',
'settings/narinfo-cache-positive-ttl.md',
'settings/netrc-file.md',
'settings/pasta-path.md',
'settings/plugin-files.md',
'settings/post-build-hook.md',
'settings/pre-build-hook.md',
@@ -332,6 +333,12 @@ elif busybox.found()
}
endif
if pasta.found()
cpp_str_defines += {
'PASTA_PATH': pasta.full_path(),
}
endif
cpp_args = []
foreach name, value : cpp_str_defines
+129 -4
View File
@@ -1,16 +1,25 @@
#include "lix/libstore/build/worker.hh"
#include "lix/libutil/cgroup.hh"
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libutil/finally.hh"
#include "lix/libstore/gc-store.hh"
#include "lix/libutil/processes.hh"
#include "lix/libutil/signals.hh"
#include "lix/libstore/platform/linux.hh"
#include "lix/libutil/regex.hh"
#include "lix/libutil/strings.hh"
#include <csignal>
#include <cstdlib>
#include <grp.h>
#include <regex>
#include <sys/prctl.h>
#if __linux__
#include <linux/capability.h>
#endif
#if HAVE_SECCOMP
#include <linux/filter.h>
#include <sys/syscall.h>
@@ -61,6 +70,14 @@ static void readFileRoots(const char * path, UncheckedRoots & roots)
}
}
LinuxLocalDerivationGoal::~LinuxLocalDerivationGoal()
{
// pasta being left around mostly happens when builds are aborted
if (pastaPid) {
pastaPid.kill();
}
}
void LinuxLocalStore::findPlatformRoots(UncheckedRoots & unchecked)
{
auto procDir = AutoCloseDir{opendir("/proc")};
@@ -836,6 +853,26 @@ void LinuxLocalDerivationGoal::prepareSandbox()
}
}
std::string LinuxLocalDerivationGoal::rewriteResolvConf(std::string fromHost)
{
if (!runPasta) {
return fromHost;
}
static constexpr auto flags = std::regex::ECMAScript | std::regex::multiline;
static auto lineRegex = regex::parse("^nameserver\\s.*$", flags);
static auto v4Regex = regex::parse("^nameserver\\s+\\d{1,3}\\.", flags);
static auto v6Regex = regex::parse("^nameserver.*:", flags);
std::string nsInSandbox = "\n";
if (std::regex_search(fromHost, v4Regex)) {
nsInSandbox += fmt("nameserver %s\n", PASTA_HOST_IPV4);
}
if (std::regex_search(fromHost, v6Regex)) {
nsInSandbox += fmt("nameserver %s\n", PASTA_HOST_IPV6);
}
return std::regex_replace(fromHost, lineRegex, "") + nsInSandbox;
}
Pid LinuxLocalDerivationGoal::startChild(std::function<void()> openSlave)
{
#if HAVE_SECCOMP
@@ -863,9 +900,11 @@ Pid LinuxLocalDerivationGoal::startChild(std::function<void()> openSlave)
- The private network namespace ensures that the builder
cannot talk to the outside world (or vice versa). It
only has a private loopback interface. (Fixed-output
derivations are not run in a private network namespace
to allow functions like fetchurl to work.)
only has a private loopback interface. If a copy of
`pasta` is available, Fixed-output derivations are run
inside a private network namespace with internet
access, otherwise they are run in the host's network
namespace, to allow functions like fetchurl to work.
- The IPC namespace prevents the builder from communicating
with outside processes using SysV IPC mechanisms (shared
@@ -886,6 +925,10 @@ Pid LinuxLocalDerivationGoal::startChild(std::function<void()> openSlave)
if (derivationType->isSandboxed())
privateNetwork = true;
// don't launch pasta unless we have a tun device. in a build sandbox we
// commonly do not, and trying to run pasta anyway naturally won't work.
runPasta = !privateNetwork && settings.pastaPath != "" && pathExists("/dev/net/tun");
userNamespaceSync.create();
Pipe sendPid;
@@ -910,7 +953,9 @@ Pid LinuxLocalDerivationGoal::startChild(std::function<void()> openSlave)
ProcessOptions options;
options.cloneFlags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
if (privateNetwork)
// we always want to create a new network namespace for pasta, even when
// we can't actually run it. not doing so hides bugs and impairs purity.
if (settings.pastaPath != "" || privateNetwork)
options.cloneFlags |= CLONE_NEWNET;
if (usingUserNamespace)
options.cloneFlags |= CLONE_NEWUSER;
@@ -980,6 +1025,67 @@ Pid LinuxLocalDerivationGoal::startChild(std::function<void()> openSlave)
/* Signal the builder that we've updated its user namespace. */
writeFull(userNamespaceSync.writeSide.get(), "1");
if (runPasta) {
// Bring up pasta, for handling FOD networking. We don't let it daemonize
// itself for process managements reasons and kill it manually when done.
// TODO add a new sandbox mode flag to disable all or parts of this?
Strings args = {
// clang-format off
"--quiet",
"--foreground",
"--config-net",
"--gateway", PASTA_HOST_IPV4,
"--address", PASTA_CHILD_IPV4, "--netmask", PASTA_IPV4_NETMASK,
"--dns-forward", PASTA_HOST_IPV4,
"--gateway", PASTA_HOST_IPV6,
"--address", PASTA_CHILD_IPV6,
"--dns-forward", PASTA_HOST_IPV6,
"--ns-ifname", PASTA_NS_IFNAME,
"--no-netns-quit",
"--netns", "/proc/self/fd/0",
// clang-format on
};
AutoCloseFD netns(open(fmt("/proc/%i/ns/net", pid.get()).c_str(), O_RDONLY | O_CLOEXEC));
if (!netns) {
throw SysError("failed to open netns");
}
AutoCloseFD userns;
if (usingUserNamespace) {
userns =
AutoCloseFD(open(fmt("/proc/%i/ns/user", pid.get()).c_str(), O_RDONLY | O_CLOEXEC));
if (!userns) {
throw SysError("failed to open userns");
}
args.push_back("--userns");
args.push_back("/proc/self/fd/1");
}
// FIXME ideally we want a notification when pasta exits, but we cannot do
// this at present. without such support we need to busy-wait for pasta to
// set up the namespace completely and time out after a while for the case
// of pasta launch failures. pasta logs go to syslog only for now as well.
pastaPid = runProgram2({
.program = settings.pastaPath,
.args = args,
.uid = useBuildUsers() ? std::optional(buildUser->getUID()) : std::nullopt,
.gid = useBuildUsers() ? std::optional(buildUser->getGID()) : std::nullopt,
// TODO these redirections are crimes. pasta closes all non-stdio file
// descriptors very early and lacks fd arguments for the namespaces we
// want it to join. we cannot have pasta join the namespaces via pids;
// doing so requires capabilities which pasta *also* drops very early.
.redirections = {
{.from = 0, .to = netns.get()},
{.from = 1, .to = userns ? userns.get() : 1},
},
.caps = getuid() == 0
? std::set<long>{CAP_SYS_ADMIN, CAP_NET_BIND_SERVICE}
: std::set<long>{},
});
}
return pid;
}
@@ -997,5 +1103,24 @@ void LinuxLocalDerivationGoal::killSandbox(bool getStats)
This avoids processes unrelated to the build being killed, thus avoiding: https://git.lix.systems/lix-project/lix/issues/667 */
LocalDerivationGoal::killSandbox(getStats);
}
if (pastaPid) {
// FIXME we really want to send SIGTERM instead and wait for pasta to exit,
// but we do not have the infra for that right now. we send SIGKILL instead
// and treat exiting with that as a successful exit code until such a time.
// this is not likely to cause problems since pasta runs as the build user,
// but not inside the build sandbox. if it's killed it's either due to some
// external influence (in which case the sandboxed child will probably fail
// due to network errors, if it used the network at all) or some bug in lix
if (auto status = pastaPid.kill(); !WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) {
if (WIFSIGNALED(status)) {
throw Error("pasta killed by signal %i", WTERMSIG(status));
} else if (WIFEXITED(status)) {
throw Error("pasta exited with code %i", WEXITSTATUS(status));
} else {
throw Error("pasta exited with status %i", status);
}
}
}
}
}
+18
View File
@@ -4,6 +4,7 @@
#include "lix/libstore/build/local-derivation-goal.hh"
#include "lix/libstore/gc-store.hh"
#include "lix/libstore/local-store.hh"
#include "lix/libutil/processes.hh"
namespace nix {
@@ -33,7 +34,23 @@ class LinuxLocalDerivationGoal : public LocalDerivationGoal
public:
using LocalDerivationGoal::LocalDerivationGoal;
~LinuxLocalDerivationGoal();
// NOTE these are all C strings because macos doesn't have constexpr std::string
// constructors, and std::string_view is a pain to turn into std::strings again.
static constexpr const char * PASTA_NS_IFNAME = "eth0";
static constexpr const char * PASTA_HOST_IPV4 = "169.254.1.1";
static constexpr const char * PASTA_CHILD_IPV4 = "169.254.1.2";
static constexpr const char * PASTA_IPV4_NETMASK = "16";
// randomly chosen 6to4 prefix, mapping the same ipv4ll as above.
// even if this id is used on the daemon host there should not be
// any collisions since ipv4ll should never be addressed by ipv6.
static constexpr const char * PASTA_HOST_IPV6 = "64:ff9b:1:4b8e:472e:a5c8:a9fe:0101";
static constexpr const char * PASTA_CHILD_IPV6 = "64:ff9b:1:4b8e:472e:a5c8:a9fe:0102";
private:
RunningProgram pastaPid;
/**
* Create and populate chroot
*/
@@ -62,6 +79,7 @@ private:
return true;
}
std::string rewriteResolvConf(std::string fromHost) override;
};
}
+1
View File
@@ -147,6 +147,7 @@ void RemoteStore::setOptions(Connection & conn)
overrides.erase(experimentalFeatureSettings.experimentalFeatures.name);
overrides.erase(settings.pluginFiles.name);
overrides.erase(settings.storeUri.name); // the daemon *is* the store
overrides.erase(settings.tarballTtl.name); // eval-time only, implictly set by flake cli
conn.to << overrides.size();
for (auto & i : overrides)
conn.to << i.first << i.second.value;
+14 -4
View File
@@ -1,14 +1,24 @@
---
name: build-dir
internalName: buildDir
settingType: PathsSetting<std::optional<Path>>
default: null
settingType: PathsSetting<Path>
defaultText: "`«nixStateDir»/builds`"
defaultExpr: nixStateDir + "/builds"
---
The directory on the host, in which derivations' temporary build directories are created.
If not set, Nix will use the [`temp-dir`](#conf-temp-dir) setting if set, otherwise the system temporary directory indicated by the `TMPDIR` environment variable.
Note that builds are often performed by the Nix daemon, so its `TMPDIR` is used, and not that of the Nix command line interface.
If not set, Lix will use the `builds` subdirectory of its configured state directory.
Lix will create this directory automatically with suitable permissions if it does not
exist, otherwise its permissions must allow all users to traverse the directory (i.e.
it must have `o+x` set, in unix parlance) for non-sandboxed builds to work correctly.
This is also the location where [`--keep-failed`](@docroot@/command-ref/opt-common.md#opt-keep-failed) leaves its files.
If Nix runs without sandbox, or if the platform does not support sandboxing with bind mounts (e.g. macOS), then the [`builder`](@docroot@/language/derivations.md#attr-builder)'s environment will contain this directory, instead of the virtual location [`sandbox-build-dir`](#conf-sandbox-build-dir).
> Important:
>
> `build-dir` must not be set to a world-writable directory. Placing temporary build
> directories in a world-writable place allows other users to access or modify build
> data that is currently in use. This alone is merely an impurity, but combined with
> another factor this has allowed malicious derivations to escape the build sandbox.
+10
View File
@@ -0,0 +1,10 @@
---
name: pasta-path
internalName: pastaPath
type: Path
default: ""
---
If set to an absolute path, enables fully sandboxing fixed-output
derivations, by using `pasta` to pass network traffic between the
private network namespace. This allows for greater levels of isolation
of builds to the host.
+6 -1
View File
@@ -179,8 +179,13 @@ SQLiteStmt::Use::~Use()
SQLiteStmt::Use & SQLiteStmt::Use::operator () (std::string_view value, bool notNull)
{
if (notNull) {
if (sqlite3_bind_text(stmt.stmt.get(), curArg++, value.data(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
if (sqlite3_bind_text(
stmt.stmt.get(), curArg++, value.data(), value.length(), SQLITE_TRANSIENT
)
!= SQLITE_OK)
{
SQLiteError::throw_(stmt.db, "binding argument");
}
} else
bind();
return *this;
+4 -34
View File
@@ -5,6 +5,7 @@
#include "lix/libutil/logging.hh"
#include "lix/libutil/strings.hh"
#include "lix/libstore/temporary-dir.hh"
#include <unistd.h>
namespace nix {
@@ -80,10 +81,11 @@ std::unique_ptr<SSH::Connection> SSH::startCommand(const std::string & command)
// reasonably POSIX-y semantics for the things we're about
// to do next.
if (fakeSSH) {
args = { "bash" };
args = { "bash", "-c", command };
} else {
args = { "ssh", host.c_str(), "-x", "-T", "-oRemoteCommand=bash" };
args = { "ssh", host.c_str(), "-x", "-T" };
addCommonSSHOpts(args);
args.push_back(command);
}
execvp(args.begin()->c_str(), stringsToCharPtrs(args).data());
@@ -96,38 +98,6 @@ std::unique_ptr<SSH::Connection> SSH::startCommand(const std::string & command)
in.readSide.reset();
out.writeSide.reset();
// Once we hand off to nix-store (on the remote) and the caller (on the client),
// we lose the ability to catch SSH failing, due to Historical Architectural Decisions.
//
// We want to catch at least _some_ errors and alert the user in case of
// an obvious misconfiguration, so run a very simple command first
// to make sure things are at least somewhat operational.
//
// The exact semantics of
// - not having a shell prompt get in the way when non-interactive
// - echo doing the reasonable thing
// Are exactly why we specifically forced bash (via ssh RemoteCommand) earlier.
// We do *not* use /bin/sh because that may be busybox and busybox breaks here.
//
// FIXME: make any of this shit make sense
{
writeLine(in.writeSide.get(), "echo started");
std::string reply;
try {
reply = readLine(out.readSide.get());
} catch (EndOfFile & e) { }
if (reply != "started") {
warn("SSH to '%s' failed, stdout first line: '%s'", host, reply);
throw Error("failed to start SSH connection to '%s'", host);
}
}
// Now that we're reasonably confident we have something vaguely resembling
// a connection, hand off to the command.
writeLine(in.writeSide.get(), fmt("exec %s", command));
conn->out = std::move(out.readSide);
conn->in = std::move(in.writeSide);
+1 -1
View File
@@ -632,7 +632,7 @@ struct AsyncCopier : AsyncInputStream
co_yield Fragment{want->n, false};
} else if (auto f = std::get_if<Parser::FileHeader>(&*i)) {
co_yield Fragment{f->size, true};
} else if (auto sl = std::get_if<Parser::Symlink>(&*i)) {
} else if (auto _ = std::get_if<Parser::Symlink>(&*i)) {
// nothing to do
} else if (auto dir = std::get_if<Parser::Directory>(&*i)) {
while (auto e = dir->content.next()) {
+1
View File
@@ -259,6 +259,7 @@ struct BrotliCompressionSink : ChunkedCompressionSink
void writeInternal(std::string_view data) override
{
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
auto next_in = charptr_cast<const uint8_t *>(data.data());
size_t avail_in = data.size();
uint8_t * next_out = outbuf;
+23 -2
View File
@@ -10,6 +10,7 @@
#include "lix/libutil/strings.hh"
#include "lix/libutil/config-impl.hh"
#include <mutex>
namespace nix {
@@ -343,11 +344,31 @@ template<> std::string BaseSetting<StringSet>::to_string() const
template<> ExperimentalFeatures BaseSetting<ExperimentalFeatures>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
auto warnDeprecated294 = [](std::once_flag & flag, std::string_view thing) {
std::call_once(flag, [&] {
warn(
"The %s experimental feature is deprecated and will be removed in Lix 2.94. "
"See https://git.lix.systems/lix-project/lix/issues/815 for more details.",
thing
);
});
};
ExperimentalFeatures res{};
for (auto & s : tokenizeString<StringSet>(str)) {
if (auto thisXpFeature = parseExperimentalFeature(s); thisXpFeature)
if (auto thisXpFeature = parseExperimentalFeature(s); thisXpFeature) {
if (*thisXpFeature == Xp::CaDerivations) {
static std::once_flag warned;
warnDeprecated294(warned, s);
} else if (*thisXpFeature == Xp::DynamicDerivations) {
static std::once_flag warned;
warnDeprecated294(warned, s);
} else if (*thisXpFeature == Xp::ImpureDerivations) {
static std::once_flag warned;
warnDeprecated294(warned, s);
}
res = res | thisXpFeature.value();
else
} else
warn("unknown experimental feature '%s'", s);
}
return res;
+23
View File
@@ -155,6 +155,29 @@ int AutoCloseFD::get() const
return fd;
}
std::string guessOrInventPathFromFD(int fd)
{
assert(fd >= 0);
/* On Linux, there's no F_GETPATH available.
* But we can read /proc/ */
#if __linux__
try {
return readLink(fmt("/proc/self/fd/%1%", fd).c_str());
} catch (...) {
}
#elif defined (HAVE_F_GETPATH) && HAVE_F_GETPATH
std::string fdName(PATH_MAX, '\0');
if (fcntl(fd, F_GETPATH, fdName.data()) != -1) {
fdName.resize(strlen(fdName.c_str()));
return fdName;
}
#else
#error "No implementation for retrieving file descriptors path."
#endif
return fmt("<fd %i>", fd);
}
void AutoCloseFD::close()
{
+18
View File
@@ -36,6 +36,15 @@ void writeFull(int fd, std::string_view s, bool allowInterrupts = true);
*/
std::string drainFD(int fd, bool block = true, const size_t reserveSize=0);
/*
* Will attempt to guess *A* path associated that might lead to the same file as used by this
* file descriptor.
*
* The returned string should NEVER be used as a valid path.
*/
std::string guessOrInventPathFromFD(int fd);
Generator<Bytes> drainFDSource(int fd, bool block = true);
class AutoCloseFD
@@ -50,6 +59,15 @@ public:
AutoCloseFD& operator =(const AutoCloseFD & fd) = delete;
AutoCloseFD& operator =(AutoCloseFD&& fd) noexcept(false);
int get() const;
/*
* Will attempt to guess *A* path associated that might lead to the same file as used by this
* file descriptor.
*
* The returned string should NEVER be used as a valid path.
*/
std::string guessOrInventPath() const { return guessOrInventPathFromFD(fd); }
explicit operator bool() const;
int release();
void close();
+55 -24
View File
@@ -359,25 +359,44 @@ Generator<Bytes> readFileSource(const Path & path)
}
void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
void writeFile(const Path & path, std::string_view s, mode_t mode)
{
AutoCloseFD fd{open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)};
if (!fd)
throw SysError("opening file '%1%'", path);
writeFile(fd, s, mode);
/* Close explicitly to propagate the exceptions. */
fd.close();
}
void writeFile(AutoCloseFD & fd, std::string_view s, mode_t mode)
{
assert(fd);
try {
writeFull(fd.get(), s);
} catch (Error & e) {
e.addTrace({}, "writing file '%1%'", path);
e.addTrace({}, "writing file '%1%'", fd.guessOrInventPath());
throw;
}
if (sync)
fd.fsync();
// Explicitly close to make sure exceptions are propagated.
fd.close();
if (sync)
syncParent(path);
}
void writeFileAndSync(const Path & path, std::string_view s, mode_t mode)
{
{
AutoCloseFD fd{open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)};
if (!fd)
throw SysError("opening file '%1%'", path);
writeFile(fd, s, mode);
fd.fsync();
/* Close explicitly to ensure that exceptions are propagated. */
fd.close();
}
syncParent(path);
}
static AutoCloseFD openForWrite(const Path & path, mode_t mode)
{
@@ -397,7 +416,7 @@ static void closeForWrite(const Path & path, AutoCloseFD & fd, bool sync)
syncParent(path);
}
void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
void writeFile(const Path & path, Source & source, mode_t mode)
{
AutoCloseFD fd = openForWrite(path, mode);
@@ -414,11 +433,11 @@ void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
e.addTrace({}, "writing file '%1%'", path);
throw;
}
closeForWrite(path, fd, sync);
closeForWrite(path, fd, false);
}
kj::Promise<Result<void>>
writeFile(const Path & path, AsyncInputStream & source, mode_t mode, bool sync)
writeFile(const Path & path, AsyncInputStream & source, mode_t mode)
try {
AutoCloseFD fd = openForWrite(path, mode);
@@ -436,7 +455,7 @@ try {
e.addTrace({}, "writing file '%1%'", path);
throw;
}
closeForWrite(path, fd, sync);
closeForWrite(path, fd, false);
co_return result::success();
} catch (...) {
co_return result::current_exception();
@@ -450,18 +469,29 @@ void syncParent(const Path & path)
fd.fsync();
}
static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed, bool interruptible)
/* TODO(horrors): a better structure that links all parent fds for the traversal root
* should be considered for this code
*/
static void _deletePath(int parentfd, const std::string & name, uint64_t & bytesFreed, bool interruptible)
{
/* This ensures that `name` is an immediate child of `parentfd`. */
assert(!name.empty() && name.find('/') == std::string::npos && "`name` is an immediate child to `parentfd`");
if (interruptible) {
checkInterrupt();
}
std::string name(baseNameOf(path));
/* FIXME(horrors): there's a minor TOCTOU here.
* we fstatat the inode nofollow, check if this is a directory
* and then open it.
* a better alternative is open it as O_PATH as a namefd.
* if it's a directory, it can be openat with the namefd.
*/
struct stat st;
if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
if (errno == ENOENT) return;
throw SysError("getting status of '%1%'", path);
throw SysError("getting status of '%1%' in directory '%2%'", name, guessOrInventPathFromFD(parentfd));
}
if (!S_ISDIR(st.st_mode)) {
@@ -492,24 +522,25 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed,
/* Make the directory accessible. */
const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR;
if ((st.st_mode & PERM_MASK) != PERM_MASK) {
if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1)
throw SysError("chmod '%1%'", path);
if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1) {
throw SysError("chmod '%1%' in directory '%2%'", name, guessOrInventPathFromFD(parentfd));
}
}
int fd = openat(parentfd, path.c_str(), O_RDONLY);
int fd = openat(parentfd, name.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
if (fd == -1)
throw SysError("opening directory '%1%'", path);
throw SysError("opening directory '%1%' in directory '%2%'", name, guessOrInventPathFromFD(parentfd));
AutoCloseDir dir(fdopendir(fd));
if (!dir)
throw SysError("opening directory '%1%'", path);
for (auto & i : readDirectory(dir.get(), path, interruptible))
_deletePath(dirfd(dir.get()), path + "/" + i.name, bytesFreed, interruptible);
throw SysError("opening directory '%1%' in directory '%2%'", name, guessOrInventPathFromFD(parentfd));
for (auto & i : readDirectory(dir.get(), name, interruptible))
_deletePath(dirfd(dir.get()), i.name, bytesFreed, interruptible);
}
int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
if (unlinkat(parentfd, name.c_str(), flags) == -1) {
if (errno == ENOENT) return;
throw SysError("cannot unlink '%1%'", path);
throw SysError("cannot unlink '%1%' in directory '%2%'", name, guessOrInventPathFromFD(parentfd));
}
}
@@ -525,7 +556,7 @@ static void _deletePath(const Path & path, uint64_t & bytesFreed, bool interrupt
throw SysError("opening directory '%1%'", path);
}
_deletePath(dirfd.get(), path, bytesFreed, interruptible);
_deletePath(dirfd.get(), std::string(baseNameOf(path)), bytesFreed, interruptible);
}
+10 -3
View File
@@ -189,11 +189,18 @@ Generator<Bytes> readFileSource(const Path & path);
/**
* Write a string to a file.
*/
void writeFile(const Path & path, std::string_view s, mode_t mode = 0666, bool sync = false);
void writeFile(const Path & path, std::string_view s, mode_t mode = 0666);
void writeFile(const Path & path, Source & source, mode_t mode = 0666, bool sync = false);
void writeFile(const Path & path, Source & source, mode_t mode = 0666);
void writeFile(AutoCloseFD & fd, std::string_view s, mode_t mode = 0666);
kj::Promise<Result<void>>
writeFile(const Path & path, AsyncInputStream & source, mode_t mode = 0666, bool sync = false);
writeFile(const Path & path, AsyncInputStream & source, mode_t mode = 0666);
/**
* Write a string to a file and flush the file and its parents direcotry to disk.
*/
void writeFileAndSync(const Path & path, std::string_view s, mode_t mode = 0666);
/**
* Flush a file's parent directory to disk
+1 -1
View File
@@ -38,6 +38,6 @@ const JSON & ensureType(
JSON(expectedType).type_name(),
value.type_name());
return value;
return value; // NOLINT(bugprone-return-const-ref-from-parameter)
}
}
+3 -3
View File
@@ -284,9 +284,9 @@ extern Verbosity verbosity;
*/
#define printMsgUsing(loggerParam, level, args...) \
do { \
auto __lvl = level; \
if (__lvl <= nix::verbosity) { \
loggerParam->log(__lvl, fmt(args)); \
auto _lix_logger_print_lvl = level; \
if (_lix_logger_print_lvl <= nix::verbosity) { \
loggerParam->log(_lix_logger_print_lvl, fmt(args)); \
} \
} while (0)
#define printMsg(level, args...) printMsgUsing(logger, level, args)
+69 -5
View File
@@ -22,6 +22,7 @@
#endif
#ifdef __linux__
# include <linux/capability.h>
# include <sys/prctl.h>
# include <sys/mman.h>
#endif
@@ -249,7 +250,7 @@ std::pair<int, std::string> runProgram(RunOptions && options)
try {
auto proc = runProgram2(options);
Finally const _wait([&] { proc.wait(); });
Finally const _wait([&] { proc.waitAndCheck(); });
stdout = proc.getStdout()->drain();
} catch (ExecError & e) {
status = e.status;
@@ -277,7 +278,22 @@ RunningProgram::~RunningProgram()
}
}
void RunningProgram::wait()
std::tuple<pid_t, std::unique_ptr<Source>, int> RunningProgram::release()
{
return {pid.release(), std::move(stdoutSource), stdout_.release()};
}
int RunningProgram::kill()
{
return pid.kill();
}
int RunningProgram::wait()
{
return pid.wait();
}
void RunningProgram::waitAndCheck()
{
if (std::uncaught_exceptions() == 0) {
int status = pid.wait();
@@ -315,12 +331,21 @@ RunningProgram runProgram2(const RunOptions & options)
replaceEnv(*options.environment);
if (options.captureStdout && dup2(out.writeSide.get(), STDOUT_FILENO) == -1)
throw SysError("dupping stdout");
if (options.mergeStderrToStdout)
if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1)
throw SysError("cannot dup stdout into stderr");
for (auto redirection : options.redirections) {
if (dup2(redirection.to, redirection.from) == -1) {
throw SysError("dupping fd %i to %i", redirection.from, redirection.to);
}
}
if (options.chdir && chdir((*options.chdir).c_str()) == -1)
throw SysError("chdir failed");
#if __linux__
if (!options.caps.empty() && prctl(PR_SET_KEEPCAPS, 1) < 0) {
throw SysError("setting keep-caps failed");
}
#endif
if (options.gid && setgid(*options.gid) == -1)
throw SysError("setgid failed");
/* Drop all other groups if we're setgid. */
@@ -329,6 +354,45 @@ RunningProgram runProgram2(const RunOptions & options)
if (options.uid && setuid(*options.uid) == -1)
throw SysError("setuid failed");
#if __linux__
if (!options.caps.empty()) {
if (prctl(PR_SET_KEEPCAPS, 0)) {
throw SysError("clearing keep-caps failed");
}
// we do the capability dance like this to avoid a dependency
// on libcap, which has a rather large build closure and many
// more features that we need for now. maybe some other time.
static constexpr uint32_t LINUX_CAPABILITY_VERSION_3 = 0x20080522;
static constexpr uint32_t LINUX_CAPABILITY_U32S_3 = 2;
struct user_cap_header_struct
{
uint32_t version;
int pid;
} hdr = {LINUX_CAPABILITY_VERSION_3, 0};
struct user_cap_data_struct
{
uint32_t effective;
uint32_t permitted;
uint32_t inheritable;
} data[LINUX_CAPABILITY_U32S_3] = {};
for (auto cap : options.caps) {
assert(cap / 32 < LINUX_CAPABILITY_U32S_3);
data[cap / 32].permitted |= 1 << (cap % 32);
data[cap / 32].inheritable |= 1 << (cap % 32);
}
if (syscall(SYS_capset, &hdr, data)) {
throw SysError("couldn't set capabilities");
}
for (auto cap : options.caps) {
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0) < 0) {
throw SysError("couldn't set ambient caps");
}
}
}
#endif
Strings args_(options.args);
args_.push_front(options.program);
+24 -2
View File
@@ -76,6 +76,11 @@ std::string runProgram(Path program, bool searchPath = false,
struct RunOptions
{
struct Redirection
{
int from, to;
};
Path program;
bool searchPath = true;
Strings args = {};
@@ -84,8 +89,11 @@ struct RunOptions
std::optional<Path> chdir = {};
std::optional<std::map<std::string, std::string>> environment = {};
bool captureStdout = false;
bool mergeStderrToStdout = false;
bool isInteractive = false;
std::vector<Redirection> redirections;
#if __linux__
std::set<long> caps;
#endif
};
struct [[nodiscard("you must call RunningProgram::wait()")]] RunningProgram
@@ -102,9 +110,23 @@ private:
public:
RunningProgram() = default;
RunningProgram(RunningProgram &&) = default;
RunningProgram & operator=(RunningProgram &&) = default;
~RunningProgram();
void wait();
explicit operator bool() const { return bool(pid); }
std::tuple<pid_t, std::unique_ptr<Source>, int> release();
int kill();
[[nodiscard]]
int wait();
void waitAndCheck();
std::optional<int> getStdoutFD() const
{
return stdout_ ? std::optional(stdout_.get()) : std::nullopt;
}
Source * getStdout() const { return stdoutSource.get(); };
};
+1 -1
View File
@@ -53,7 +53,7 @@ void RefScanSink::operator () (std::string_view data)
previous fragment and the start of the current fragment. */
auto s = tail;
auto tailLen = std::min(data.size(), refLength);
s.append(data.data(), tailLen);
s.append(data.data(), tailLen); // NOLINT(bugprone-suspicious-stringview-data-usage)
search(s, hashes, seen);
search(data, hashes, seen);
+9 -3
View File
@@ -69,12 +69,18 @@ typename T::mapped_type * get(T & map, const typename T::key_type & key)
* Get a value for the specified key from an associate container, or a default value if the key isn't present.
*/
template<class T>
const typename T::mapped_type & getOr(T & map,
const typename T::mapped_type & getOr(
T & map [[clang::lifetimebound]],
const typename T::key_type & key,
const typename T::mapped_type & defaultValue)
const typename T::mapped_type & defaultValue [[clang::lifetimebound]]
)
{
auto i = map.find(key);
if (i == map.end()) return defaultValue;
if (i == map.end()) {
/* FIXME(Raito): `[[clang::lifetimebound]]` has no effect on `defaultValue` warning. */
// NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter)
return defaultValue;
}
return i->second;
}
+12
View File
@@ -265,6 +265,11 @@ configdata += {
'HAVE_SECCOMP': seccomp.found().to_int(),
}
# fcntl(F_GETPATH) returns the path of an fd on macOS and BSDs
configdata += {
'HAVE_F_GETPATH': cxx.has_header_symbol('fcntl.h', 'F_GETPATH').to_int(),
}
libarchive = dependency('libarchive', required : true, include_type : 'system')
brotli = [
@@ -446,6 +451,13 @@ configdata += {
'HAVE_DTRACE': dtrace_feature.enabled().to_int(),
}
pasta_path = get_option('pasta-path')
# we can't check the pasta version because passt misuses stdio (it calls _exit()
# after printing the version, which will never print the version unless run from
# a terminal). pasta isn't mandatory yet due to high fetcher breakage potential.
# we *will* enable it in our own packaging, but distributions are not forced to.
pasta = find_program(pasta_path, required : false, native : false)
lsof = find_program('lsof', native : true)
# This is how Nix does generated headers...
+4
View File
@@ -24,6 +24,10 @@ option('sandbox-shell', type : 'string', value : 'busybox',
description : 'path to a statically-linked shell to use as /bin/sh in sandboxes (usually busybox)',
)
option('pasta-path', type : 'string', value : 'pasta',
description : 'path to the location of pasta (provided by passt)',
)
option('enable-tests', type : 'boolean', value : true,
description : 'whether to enable tests or not (requires rapidcheck and gtest)',
)
+64
View File
@@ -0,0 +1,64 @@
{
lib,
stdenv,
buildPackages,
fetchurl,
getconf,
gitUpdater,
testers,
}:
stdenv.mkDerivation (finalAttrs: {
pname = "passt";
version = "2025_02_17.a1e48a0";
src = fetchurl {
url = "https://passt.top/passt/snapshot/passt-${finalAttrs.version}.tar.gz";
hash = "sha256-/FUXxeYv3Lb0DiXmbS2PUzfLL5ZwHJ42tiuH7YnlljE=";
};
postPatch = ''
substituteInPlace Makefile --replace-fail \
'PAGE_SIZE=$(shell getconf PAGE_SIZE)' \
"PAGE_SIZE=$(${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe getconf} PAGE_SIZE)"
'';
makeFlags = [
"prefix=${placeholder "out"}"
"VERSION=${finalAttrs.version}"
];
passthru = {
tests.version = testers.testVersion {
package = finalAttrs.finalPackage;
};
updateScript = gitUpdater {
url = "https://passt.top/passt";
};
};
meta = with lib; {
homepage = "https://passt.top/passt/about/";
description = "Plug A Simple Socket Transport";
longDescription = ''
passt implements a translation layer between a Layer-2 network interface
and native Layer-4 sockets (TCP, UDP, ICMP/ICMPv6 echo) on a host.
It doesn't require any capabilities or privileges, and it can be used as
a simple replacement for Slirp.
pasta (same binary as passt, different command) offers equivalent
functionality, for network namespaces: traffic is forwarded using a tap
interface inside the namespace, without the need to create further
interfaces on the host, hence not requiring any capabilities or
privileges.
'';
license = [
licenses.bsd3 # and
licenses.gpl2Plus
];
platforms = platforms.linux;
maintainers = with maintainers; [ _8aed ];
mainProgram = "passt";
};
})
+1
View File
@@ -1 +1,2 @@
d @localstatedir@/nix/daemon-socket 0755 root root - -
d @localstatedir@/nix/builds 0755 root root 7d -
+8
View File
@@ -45,6 +45,8 @@
ninja,
ncurses,
openssl,
# FIXME: we need passt 2024_12_11.09478d5 or newer, i.e. nixos 25.05 or later
passt-lix ? __forDefaults.passt-lix,
pegtl,
pkg-config,
python3,
@@ -116,6 +118,8 @@
# needs derivation patching to add debuginfo and coroutine library support
# !! must build this with clang as it is affected by the gcc coroutine bugs
capnproto-lix = callPackage ./misc/capnproto.nix { inherit stdenv; };
passt-lix = callPackage ./misc/passt.nix { };
},
}:
@@ -247,6 +251,7 @@ stdenv.mkDerivation (finalAttrs: {
# which don't actually get added to PATH. And buildInputs is correct over
# nativeBuildInputs since this should be a busybox executable on the host.
"-Dsandbox-shell=${lib.getExe' busybox-sandbox-shell "busybox"}"
"-Dpasta-path=${lib.getExe' passt-lix "pasta"}"
]
++ lib.optional hostPlatform.isStatic "-Denable-embedded-sandbox-shell=true"
++ lib.optional (finalAttrs.dontBuild && !lintInsteadOfBuild) "-Denable-build=false"
@@ -263,6 +268,8 @@ stdenv.mkDerivation (finalAttrs: {
(lib.mesonBool "werror" werror)
]
++ lib.optional (hostPlatform != buildPlatform) "--cross-file=${mesonCrossFile}"
# Temporary workaround for https://git.lix.systems/lix-project/lix/issues/832
++ lib.optional (hostPlatform.isDarwin) "-Db_lto=false"
++ sanitizeOpts;
# We only include CMake so that Meson can locate toml11, which only ships CMake dependency metadata.
@@ -330,6 +337,7 @@ stdenv.mkDerivation (finalAttrs: {
++ lib.optionals hostPlatform.isLinux [
libseccomp
busybox-sandbox-shell
passt-lix
]
++ lib.optionals (
stdenv.hostPlatform.isDarwin && lib.versionOlder stdenv.hostPlatform.darwinSdkVersion "11.0"
+6
View File
@@ -65,6 +65,12 @@ in
environment.sessionVariables = {
GARAGE_ADMIN_TOKEN = "UkLeGWEvHnXBqnueR3ISEMWpOnm40jH2tM2HnnL/0F4=";
};
# ≥ v6.12 kernel has a system wide corruption related to 9p. wait until
# https://lore.kernel.org/all/w5ap2zcsatkx4dmakrkjmaexwh3mnmgc5vhavb2miaj6grrzat@7kzr5vlsrmh5/
# resolves. once this is resolved and the fix lands in a stable kernel
# in nixpkgs, this pin can be removed.
boot.kernelPackages = pkgs.linuxPackages_6_6;
};
};
testScript = ''
@@ -8,7 +8,6 @@ requireSandboxSupport
[[ $busybox =~ busybox ]] || skipTest "no busybox"
unset NIX_STORE_DIR
unset NIX_STATE_DIR
# We first build a dependency of the derivation we eventually want to
# build.
@@ -2,7 +2,6 @@ requireSandboxSupport
[[ $busybox =~ busybox ]] || skipTest "no busybox"
unset NIX_STORE_DIR
unset NIX_STATE_DIR
remoteDir=$TEST_ROOT/remote
-1
View File
@@ -3,7 +3,6 @@ requireSandboxSupport
# Avoid store dir being inside sandbox build-dir
unset NIX_STORE_DIR
unset NIX_STATE_DIR
function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }
-21
View File
@@ -49,27 +49,6 @@ test_custom_build_dir() {
}
test_custom_build_dir
test_custom_temp_dir() {
# like test_custom_build_dir(), but uses the temp-dir setting instead
# build-dir inherits from temp-dir when build-dir is unset
local customTempDir="$TEST_ROOT/custom-temp-dir"
mkdir "$customTempDir"
nix-build check.nix -A failed --argstr checkBuildId $checkBuildId \
--no-out-link --keep-failed --option temp-dir "$customTempDir" 2> $TEST_ROOT/log || status=$?
[ "$status" = "100" ]
[[ 1 == "$(count "$customTempDir/nix-build-"*)" ]]
local buildDir="$customTempDir/nix-build-"*
grep $checkBuildId $buildDir/checkBuildId
# also check a separate code path that doesn't involve build-dir
# nix-shell uses temp-dir for its rcfile path
rcpath=$(NIX_BUILD_SHELL=$SHELL nix-shell check.nix -A deterministic --option temp-dir "$customTempDir" --run 'echo $0' 2> $TEST_ROOT/log)
# rcpath is <temp-dir>/nix-shell-*/rc
[[ $rcpath = "$customTempDir"/* ]]
}
test_custom_temp_dir
test_shell_preserves_tmpdir() {
# ensure commands that spawn interactive shells don't overwrite TMPDIR with temp-dir
local envTempDir=$TEST_ROOT/shell-temp-dir-env
+7 -4
View File
@@ -4,6 +4,13 @@ needLocalStore "“min-free” and “max-free” are daemon options"
clearStore
fake_free=$TEST_ROOT/fake-free
export _NIX_TEST_FREE_SPACE_FILE=$fake_free
echo 1100 > $fake_free
# Check that auto-GC during evaluation progresses.
timeout --signal=KILL 10s nix eval --expr 'builtins.toFile "meow" "meow"' --min-free 2000
garbage1=$(nix store add-path --name garbage1 ./nar-access.sh)
garbage2=$(nix store add-path --name garbage2 ./nar-access.sh)
garbage3=$(nix store add-path --name garbage3 ./nar-access.sh)
@@ -11,10 +18,6 @@ garbage3=$(nix store add-path --name garbage3 ./nar-access.sh)
ls -l $garbage3
POSIXLY_CORRECT=1 du $garbage3
fake_free=$TEST_ROOT/fake-free
export _NIX_TEST_FREE_SPACE_FILE=$fake_free
echo 1100 > $fake_free
fifoLock=$TEST_ROOT/fifoLock
mkfifo "$fifoLock"
-1
View File
@@ -10,7 +10,6 @@ unshare --mount --map-root-user bash <<EOF
# Avoid store dir being inside sandbox build-dir
unset NIX_STORE_DIR
unset NIX_STATE_DIR
setLocalStore () {
export NIX_REMOTE=\$TEST_ROOT/\$1
+11 -10
View File
@@ -107,9 +107,9 @@ class NixSettings:
field_may("experimental-features", self.experimental_features)
field_may("store", self.store)
assert (
self.store or self.nix_store_dir
), "Failing to set either nix_store_dir or store will cause accidental use of the system store."
assert self.store or self.nix_store_dir, (
"Failing to set either nix_store_dir or store will cause accidental use of the system store."
)
return config
def to_env_overlay(self) -> dict[str, str]:
@@ -215,13 +215,14 @@ class Nix:
def nix(self, cmd: list[str], nix_exe: str = "nix", flake: bool = False) -> NixCommand:
return self.nix_cmd([nix_exe, *cmd], flake=flake)
nix_build = partialmethod(nix, nix_exe="nix-build")
nix_shell = partialmethod(nix, nix_exe="nix-shell")
nix_store = partialmethod(nix, nix_exe="nix-store")
nix_env = partialmethod(nix, nix_exe="nix-env")
nix_instantiate = partialmethod(nix, nix_exe="nix-instantiate")
nix_channel = partialmethod(nix, nix_exe="nix-channel")
nix_prefetch_url = partialmethod(nix, nix_exe="nix-prefetch-url")
# Mark each of these as correct as they are not ClassVars, but we also don't want to turn off RUF045
nix_build = partialmethod(nix, nix_exe="nix-build") # noqa: RUF045
nix_shell = partialmethod(nix, nix_exe="nix-shell") # noqa: RUF045
nix_store = partialmethod(nix, nix_exe="nix-store") # noqa: RUF045
nix_env = partialmethod(nix, nix_exe="nix-env") # noqa: RUF045
nix_instantiate = partialmethod(nix, nix_exe="nix-instantiate") # noqa: RUF045
nix_channel = partialmethod(nix, nix_exe="nix-channel") # noqa: RUF045
nix_prefetch_url = partialmethod(nix, nix_exe="nix-prefetch-url") # noqa: RUF045
def eval(self, expr: str, settings: NixSettings | None = None) -> CommandResult:
if settings is None:
-90
View File
@@ -1,90 +0,0 @@
# Nix is a sandboxed build system. But Not everything can be handled inside its
# sandbox: Network access is normally blocked off, but to download sources, a
# trapdoor has to exist. Nix handles this by having "Fixed-output derivations".
# The detail here is not important, but in our case it means that the hash of
# the output has to be known beforehand. And if you know that, you get a few
# rights: you no longer run inside a special network namespace!
#
# Now, Linux has a special feature, that not many other unices do: Abstract
# unix domain sockets! Not only that, but those are namespaced using the
# network namespace! That means that we have a way to create sockets that are
# available in every single fixed-output derivation, and also all processes
# running on the host machine! Now, this wouldn't be that much of an issue, as,
# well, the whole idea is that the output is pure, and all processes in the
# sandbox are killed before finalizing the output. What if we didn't need those
# processes at all? Unix domain sockets have a semi-known trick: you can pass
# file descriptors around!
# This makes it possible to exfiltrate a file-descriptor with write access to
# $out outside of the sandbox. And that file-descriptor can be used to modify
# the contents of the store path after it has been registered.
{ config, ... }:
let
pkgs = config.nodes.machine.nixpkgs.pkgs;
# Simple C program that sends a a file descriptor to `$out` to a Unix
# domain socket.
# Compiled statically so that we can easily send it to the VM and use it
# inside the build sandbox.
sender = pkgs.runCommandWith {
name = "sender";
stdenv = pkgs.pkgsStatic.stdenv;
} ''
$CC -static -o $out ${./sender.c}
'';
# Okay, so we have a file descriptor shipped out of the FOD now. But the
# Nix store is read-only, right? .. Well, yeah. But this file descriptor
# lives in a mount namespace where it is not! So even when this file exists
# in the actual Nix store, we're capable of just modifying its contents...
smuggler = pkgs.writeCBin "smuggler" (builtins.readFile ./smuggler.c);
# The abstract socket path used to exfiltrate the file descriptor
socketName = "FODSandboxExfiltrationSocket";
in
{
name = "ca-fd-leak";
nodes.machine =
{ config, lib, pkgs, ... }:
{ virtualisation.writableStore = true;
nix.settings.substituters = lib.mkForce [ ];
virtualisation.additionalPaths = [ pkgs.busybox-sandbox-shell sender smuggler pkgs.socat ];
};
testScript = { nodes }: ''
start_all()
machine.succeed("echo hello")
# Start the smuggler server
machine.succeed("${smuggler}/bin/smuggler ${socketName} >&2 &")
# Build the smuggled derivation.
# This will connect to the smuggler server and send it the file descriptor
machine.succeed(r"""
nix-build -E '
builtins.derivation {
name = "smuggled";
system = builtins.currentSystem;
# look ma, no tricks!
outputHashMode = "flat";
outputHashAlgo = "sha256";
outputHash = builtins.hashString "sha256" "hello, world\n";
builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
args = [ "-c" "echo \"hello, world\" > $out; ''${${sender}} ${socketName}" ];
}'
""".strip())
# Tell the smuggler server that we're done
machine.execute("echo done | ${pkgs.socat}/bin/socat - ABSTRACT-CONNECT:${socketName}")
# Check that the file was not modified
machine.succeed(r"""
cat ./result
test "$(cat ./result)" = "hello, world"
""".strip())
'';
}
-65
View File
@@ -1,65 +0,0 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
int main(int argc, char **argv) {
assert(argc == 2);
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
// Set up a abstract domain socket path to connect to.
struct sockaddr_un data;
data.sun_family = AF_UNIX;
data.sun_path[0] = 0;
strcpy(data.sun_path + 1, argv[1]);
// Now try to connect, To ensure we work no matter what order we are
// executed in, just busyloop here.
int res = -1;
while (res < 0) {
res = connect(sock, (const struct sockaddr *)&data,
offsetof(struct sockaddr_un, sun_path)
+ strlen(argv[1])
+ 1);
if (res < 0 && errno != ECONNREFUSED) perror("connect");
if (errno != ECONNREFUSED) break;
}
// Write our message header.
struct msghdr msg = {0};
msg.msg_control = malloc(128);
msg.msg_controllen = 128;
// Write an SCM_RIGHTS message containing the output path.
struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg);
hdr->cmsg_len = CMSG_LEN(sizeof(int));
hdr->cmsg_level = SOL_SOCKET;
hdr->cmsg_type = SCM_RIGHTS;
int fd = open(getenv("out"), O_RDWR | O_CREAT, 0640);
memcpy(CMSG_DATA(hdr), (void *)&fd, sizeof(int));
msg.msg_controllen = CMSG_SPACE(sizeof(int));
// Write a single null byte too.
msg.msg_iov = malloc(sizeof(struct iovec));
msg.msg_iov[0].iov_base = "";
msg.msg_iov[0].iov_len = 1;
msg.msg_iovlen = 1;
// Send it to the othher side of this connection.
res = sendmsg(sock, &msg, 0);
if (res < 0) perror("sendmsg");
int buf;
// Wait for the server to close the socket, implying that it has
// received the commmand.
recv(sock, (void *)&buf, sizeof(int), 0);
}
-66
View File
@@ -1,66 +0,0 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
int main(int argc, char **argv) {
assert(argc == 2);
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
// Bind to the socket.
struct sockaddr_un data;
data.sun_family = AF_UNIX;
data.sun_path[0] = 0;
strcpy(data.sun_path + 1, argv[1]);
int res = bind(sock, (const struct sockaddr *)&data,
offsetof(struct sockaddr_un, sun_path)
+ strlen(argv[1])
+ 1);
if (res < 0) perror("bind");
res = listen(sock, 1);
if (res < 0) perror("listen");
int smuggling_fd = -1;
// Accept the connection a first time to receive the file descriptor.
fprintf(stderr, "%s\n", "Waiting for the first connection");
int a = accept(sock, 0, 0);
if (a < 0) perror("accept");
struct msghdr msg = {0};
msg.msg_control = malloc(128);
msg.msg_controllen = 128;
// Receive the file descriptor as sent by the smuggler.
recvmsg(a, &msg, 0);
struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg);
while (hdr) {
if (hdr->cmsg_level == SOL_SOCKET
&& hdr->cmsg_type == SCM_RIGHTS) {
// Grab the copy of the file descriptor.
memcpy((void *)&smuggling_fd, CMSG_DATA(hdr), sizeof(int));
}
hdr = CMSG_NXTHDR(&msg, hdr);
}
fprintf(stderr, "%s\n", "Got the file descriptor. Now waiting for the second connection");
close(a);
// Wait for a second connection, which will tell us that the build is
// done
a = accept(sock, 0, 0);
fprintf(stderr, "%s\n", "Got a second connection, rewriting the file");
// Write a new content to the file
if (ftruncate(smuggling_fd, 0)) perror("ftruncate");
char * new_content = "Pwned\n";
int written_bytes = write(smuggling_fd, new_content, strlen(new_content));
if (written_bytes != strlen(new_content)) perror("write");
}
+8 -21
View File
@@ -67,6 +67,8 @@ in
};
});
# Let's ensure that reasonably popular shells are tested for remote building.
remoteBuildsNushell = runNixOSTestFor "x86_64-linux" ({ lib, pkgs, ... }: {
name = "remoteBuilds_nushell";
imports = [ ./remote-builds.nix ];
@@ -75,27 +77,11 @@ in
};
});
remoteBuildsWeirdShell = runNixOSTestFor "x86_64-linux" ({ lib, pkgs, ... }: {
name = "remoteBuilds_weird_shell";
remoteBuildsBusybox = runNixOSTestFor "x86_64-linux" ({ lib, pkgs, ... }: {
name = "remoteBuilds_busybox";
imports = [ ./remote-builds.nix ];
builders.config = { lib, pkgs, ... }: {
# a pathologically weird shell that can do nothing BUT run bash
users.users.root.shell = pkgs.writeTextFile {
name = "watsh";
destination = "/bin/watsh";
executable = true;
text = ''
#!/bin/sh
if [ "$1" = "-c" ] && [ "$2" = "bash" ]; then
exec bash
else
echo "Wat."
fi
'';
passthru.shellPath = "/bin/watsh";
};
users.users.root.shell = pkgs.busybox;
};
});
@@ -179,12 +165,13 @@ in
["i686-linux" "x86_64-linux"]
(system: runNixOSTestFor system ./setuid/setuid.nix);
ca-fd-leak = runNixOSTestFor "x86_64-linux" ./ca-fd-leak;
fetch-git = runNixOSTestFor "x86_64-linux" ./fetch-git;
symlinkResolvconf = runNixOSTestFor "x86_64-linux" ./symlink-resolvconf.nix;
# Use this test to test things that cannot easily be tested under chroot Nix stores in functional test suite.
non-chroot-misc = runNixOSTestFor "x86_64-linux" ./non-chroot-misc;
noNewPrivilegesInSandbox = runNixOSTestFor "x86_64-linux" ./no-new-privileges/sandbox.nix;
noNewPrivilegesOutsideSandbox = runNixOSTestFor "x86_64-linux" ./no-new-privileges/no-sandbox.nix;
+2 -2
View File
@@ -52,7 +52,7 @@ in
security.pki.certificateFiles = [ "${goodCert}/cert.pem" ];
networking.hosts."127.0.0.1" = [ "good" "bad" ];
networking.hosts."192.168.1.1" = [ "good" "bad" ];
virtualisation.writableStore = true;
@@ -76,7 +76,7 @@ in
# Fetching from a server with an untrusted cert should fail.
err = machine.fail("nix build --no-substitute --expr 'import <nix/fetchurl.nix> { url = \"https://bad/index.html\"; hash = \"sha256-rsBwZF/lPuOzdjBZN2E08FjMM3JHyXit0Xi2zN+wAZ8=\"; }' 2>&1")
print(err)
assert "SSL peer certificate or SSH remote key was not OK" in err
assert "SSL certificate problem: self-signed certificate" in err
# Fetching from a server with a trusted cert should work via environment variable override.
machine.succeed("NIX_SSL_CERT_FILE=/tmp/cafile.pem nix build --no-substitute --expr 'import <nix/fetchurl.nix> { url = \"https://bad/index.html\"; hash = \"sha256-rsBwZF/lPuOzdjBZN2E08FjMM3JHyXit0Xi2zN+wAZ8=\"; }'")
+2 -2
View File
@@ -45,12 +45,12 @@ in {
server.wait_for_unit("network-online.target")
client.wait_for_unit("network-online.target")
client.succeed("mkdir -m 700 /root/.ssh")
client.succeed("mkdir -m 700 /root/.ssh || [[ -d /root/.ssh ]]")
client.copy_from_host("key", "/root/.ssh/id_ed25519")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
# Install the SSH key on the server.
server.succeed("mkdir -m 700 /root/.ssh")
server.succeed("mkdir -m 700 /root/.ssh || [[ -d /root/.ssh ]]")
server.copy_from_host("key.pub", "/root/.ssh/authorized_keys")
server.wait_for_unit("sshd.service")
client.succeed(f"ssh -o StrictHostKeyChecking=no {server.name} 'echo hello world' >&2")
+34
View File
@@ -0,0 +1,34 @@
{ ... }:
# Misc things we want to test inside of a non redirected, non chroot Nix store.
let
nonAutoCleaningFailingDerivationCode = ''
derivation {
name = "scratch-failing";
system = builtins.currentSystem;
builder = "/bin/sh";
args = [ (builtins.toFile "builder.sh" "echo bonjour > $out; echo out: $out; false") ];
}
'';
in
{
name = "non-chroot-sandbox-misc";
nodes.machine = {
};
testScript = { nodes }: ''
import re
start_all()
# You might ask yourself why write such a convoluted thing?
# The condition for fooling Nix into NOT cleaning up the output path are non trivial and unclear.
# This is one of those: create a derivation, mkdir or touch the $out path, communicate it back.
# Even with a sandboxed Lix, you will observe leftovers before 2.93.0. After this version, this test passes.
result = machine.fail("""nix-build --substituters "" -E '${nonAutoCleaningFailingDerivationCode}' 2>&1""")
match = re.search(r'out: (\S+)', result)
assert match is not None, "Did not find Nix store path in the result of the failing build"
outpath = match.group(1).strip()
print(f"Found Nix store path: {outpath}")
machine.fail(f'stat {outpath}')
'';
}
+1 -1
View File
@@ -98,7 +98,7 @@ in
out = client.fail("nix-build ${expr nodes.client 1} 2>&1")
assert "Host key verification failed." in out, f"No host verification error:\n{out}"
assert "warning: SSH to 'root@builder' failed, stdout first line: '''" in out, f"No details about which host:\n{out}"
assert "'ssh-ng://root@builder'" in out, f"No details about which host:\n{out}"
client.succeed(f"ssh -o StrictHostKeyChecking=no {builder.name} 'echo hello world' >&2")
+81
View File
@@ -3,6 +3,8 @@
#include "lix/libutil/strings.hh"
#include "lix/libutil/types.hh"
#include "lix/libutil/terminal.hh"
#include "lix/libutil/unix-domain-socket.hh"
#include "tests/test-data.hh"
#include <gtest/gtest.h>
@@ -207,6 +209,85 @@ namespace nix {
ASSERT_FALSE(pathExists("/schnitzel/darmstadt/pommes"));
}
/* ----------------------------------------------------------------------------
* AutoCloseFD::guessOrInventPath
* --------------------------------------------------------------------------*/
void testGuessOrInventPathPrePostDeletion(AutoCloseFD & fd, Path & path) {
{
SCOPED_TRACE(fmt("guessing path before deletion of '%1%'", path));
ASSERT_TRUE(fd);
/* We cannot predict what the platform will return here.
* But it cannot fail. */
ASSERT_TRUE(fd.guessOrInventPath().size() >= 0);
}
{
SCOPED_TRACE(fmt("guessing path after deletion of '%1%'", path));
deletePath(path);
/* We cannot predict what the platform will return here.
* But it cannot fail. */
ASSERT_TRUE(fd.guessOrInventPath().size() >= 0);
}
}
TEST(guessOrInventPath, files) {
Path filePath = getUnitTestDataPath("guess-or-invent/test.txt");
createDirs(dirOf(filePath));
writeFile(filePath, "some text");
AutoCloseFD file{open(filePath.c_str(), O_RDONLY, 0666)};
testGuessOrInventPathPrePostDeletion(file, filePath);
}
TEST(guessOrInventPath, directories) {
Path dirPath = getUnitTestDataPath("guess-or-invent/test-dir");
createDirs(dirPath);
AutoCloseFD directory{open(dirPath.c_str(), O_DIRECTORY, 0666)};
testGuessOrInventPathPrePostDeletion(directory, dirPath);
}
#ifdef O_PATH
TEST(guessOrInventPath, symlinks) {
Path symlinkPath = getUnitTestDataPath("guess-or-invent/test-symlink");
Path targetPath = getUnitTestDataPath("guess-or-invent/nowhere");
createDirs(dirOf(symlinkPath));
createSymlink(targetPath, symlinkPath);
AutoCloseFD symlink{open(symlinkPath.c_str(), O_PATH | O_NOFOLLOW, 0666)};
testGuessOrInventPathPrePostDeletion(symlink, symlinkPath);
}
TEST(guessOrInventPath, fifos) {
Path fifoPath = getUnitTestDataPath("guess-or-invent/fifo");
createDirs(dirOf(fifoPath));
ASSERT_TRUE(mkfifo(fifoPath.c_str(), 0666) == 0);
AutoCloseFD fifo{open(fifoPath.c_str(), O_PATH | O_NOFOLLOW, 0666)};
testGuessOrInventPathPrePostDeletion(fifo, fifoPath);
}
#endif
TEST(guessOrInventPath, pipes) {
int pipefd[2];
ASSERT_TRUE(pipe(pipefd) == 0);
AutoCloseFD pipe_read{pipefd[0]};
ASSERT_TRUE(pipe_read);
AutoCloseFD pipe_write{pipefd[1]};
ASSERT_TRUE(pipe_write);
/* We cannot predict what the platform will return here.
* But it cannot fail. */
ASSERT_TRUE(pipe_read.guessOrInventPath().size() >= 0);
ASSERT_TRUE(pipe_write.guessOrInventPath().size() >= 0);
pipe_write.close();
ASSERT_TRUE(pipe_read.guessOrInventPath().size() >= 0);
pipe_read.close();
}
TEST(guessOrInventPath, sockets) {
Path socketPath = getUnitTestDataPath("guess-or-invent/socket");
createDirs(dirOf(socketPath));
AutoCloseFD socket = createUnixDomainSocket(socketPath, 0666);
testGuessOrInventPathPrePostDeletion(socket, socketPath);
}
/* ----------------------------------------------------------------------------
* concatStringsSep
* --------------------------------------------------------------------------*/
+2 -2
View File
@@ -1,5 +1,5 @@
{
"version": "2.93.0",
"official_release": false,
"version": "2.93.1",
"official_release": true,
"release_name": "Bici Bici"
}