Compare commits

...

14 commits

Author SHA1 Message Date
alois31 ddfe379a6b Merge "libstore/build: always enable seccomp filtering and no-new-privileges" into main 2024-05-25 04:21:53 +00:00
Qyriad 5b4b216fac Merge "nix3-upgrade-nix: fix when there are differing pnames" into main 2024-05-25 02:20:24 +00:00
Qyriad 1c0f3c540e Merge changes I3ab84cc5,Iba34ad42 into main
* changes:
  nix3: always use the same verbosity default (info)
  libfetchers: log fetches by URL just before they happen
2024-05-25 01:52:44 +00:00
Qyriad f3f68fcfac nix3-upgrade-nix: fix when there are differing pnames
Change-Id: I19c7e24a4d46137127e76b7bb133e0184d73d1b6
2024-05-25 01:50:32 +00:00
Qyriad 076c19e0d1 change "evaluating file" logs to debug
I can't imagine wanting this unless you are debugging something (in
which case it's very useful)

Change-Id: I90c6f182c18486e9f6b15a59379bbb8e88fb8e7f
2024-05-24 23:03:08 +00:00
alois31 f047e4357b libstore/build: always enable seccomp filtering and no-new-privileges
Seccomp filtering and the no-new-privileges functionality improve the security
of the sandbox, and have been enabled by default for a long time. In
lix-project/lix#265 it was decided that they
should be enabled unconditionally. Accordingly, remove the allow-new-privileges
(which had weird behavior anyway) and filter-syscall settings, and force the
security features on. Syscall filtering can still be enabled at build time to
support building on architectures libseccomp doesn't support.

Change-Id: Iedbfa18d720ae557dee07a24f69b2520f30119cb
2024-05-24 21:19:29 +00:00
Qyriad d0390b5cf2 nix3: always use the same verbosity default (info)
Change-Id: I3ab84cc583e3e8b1c05a8ae1a7a087204f513d03
2024-05-24 15:15:42 -06:00
Qyriad 8c06b7b431 libfetchers: log fetches by URL just before they happen
Addresses but does not close #305, as we still need an indicator for
frozen fetches.

Change-Id: Iba34ad42dc1c8772f7da249b90fe794b041bbf73
2024-05-24 15:15:42 -06:00
jade 19ea351642 Merge "clang-tidy: work with angle brackets and external projects" into main 2024-05-24 19:11:26 +00:00
Qyriad adfc22c3e3 Merge "make CTRL+Z work in the REPL" into main 2024-05-24 17:37:33 +00:00
Qyriad 933f1f48a2 Merge "justfile: remove --quiet from just test" into main 2024-05-24 17:37:25 +00:00
Qyriad 65da3e7199 make CTRL+Z work in the REPL
Editline just wasn't being built with --enable-sigstop lol

Change-Id: I35a78f74ea100d97f26b2b41990deb373fd9cd9a
2024-05-24 03:10:12 +00:00
jade 745b5d3d4f clang-tidy: work with angle brackets and external projects
Also fix the readme

Change-Id: I422dff5536bf01d43983621aa01035bd77ac0252
2024-05-24 02:22:58 +00:00
Qyriad bb6d43b63b justfile: remove --quiet from just test
`meson test` refuses to let `--verbose` (which shows the entire
invocation and stdio) override `--quiet`, but if neither are specified
in the justfile then you can use either `just test -q` or `just test -v`

Change-Id: I449e13084ce64666b7ee2ab4280818782fb8185a
2024-05-24 02:22:25 +00:00
26 changed files with 158 additions and 112 deletions

View file

@ -44,32 +44,41 @@ void FixIncludesCallbacks::LexedFileChanged(FileID, LexedFileChangeReason,
}
void FixIncludesCallbacks::InclusionDirective(
SourceLocation, const Token &, StringRef, bool,
SourceLocation, const Token &, StringRef FileName, bool IsAngled,
CharSourceRange FilenameRange, OptionalFileEntryRef File, StringRef,
StringRef, const Module *, SrcMgr::CharacteristicKind) {
if (Ignore)
return;
// FIXME: this is kinda evil, but this is a one-time fixup
const std::string SourceDir = "src/";
const std::vector<std::string> SourceDirs = {"src/", "include/lix/"};
if (File && File->getNameAsRequested().contains(SourceDir)) {
StringRef Name = File->getNameAsRequested();
auto Idx = Name.find(SourceDir);
assert(Idx != std::string::npos);
StringRef Suffix = Name.drop_front(Idx + SourceDir.length());
const auto Bracketize = [IsAngled](StringRef s) {
return IsAngled ? ("<" + s + ">").str() : ("\"" + s + "\"").str();
};
if (!Suffix.starts_with("lib")) {
llvm::dbgs() << "ignored: " << Suffix << "\n";
return;
for (const auto &SourceDir : SourceDirs) {
const bool IsAlreadyFixed = FileName.starts_with("lix/lib");
if (File && File->getNameAsRequested().contains(SourceDir) &&
!IsAlreadyFixed) {
StringRef Name = File->getNameAsRequested();
auto Idx = Name.find(SourceDir);
assert(Idx != std::string::npos);
std::string Suffix = Name.drop_front(Idx + SourceDir.length()).str();
if (!Suffix.starts_with("lib")) {
llvm::dbgs() << "ignored: " << Suffix << "\n";
return;
}
Suffix = "lix/" + Suffix;
auto Diag = Check.diag(FilenameRange.getBegin(),
"include needs to specify the source subdir");
Diag << FilenameRange
<< FixItHint::CreateReplacement(FilenameRange, Bracketize(Suffix));
}
auto Diag = Check.diag(FilenameRange.getBegin(),
"include needs to specify the source subdir");
Diag << FilenameRange
<< FixItHint::CreateReplacement(FilenameRange,
("\"" + Suffix + "\"").str());
}
}

View file

@ -1,6 +1,6 @@
# Clang tidy lints for Nix
# Clang tidy lints for Lix
This is a skeleton of a clang-tidy lints library for Nix.
This is a skeleton of a clang-tidy lints library for Lix.
Currently there is one check (which is already obsolete as it has served its
goal and is there as an example), `HasPrefixSuffixCheck`.
@ -10,13 +10,13 @@ goal and is there as an example), `HasPrefixSuffixCheck`.
One file:
```
ninja -C build && clang-tidy --checks='-*,nix-*' --load=build/libnix-clang-tidy.so -p ../compile_commands.json --fix ../src/libcmd/installables.cc
ninja -C build && clang-tidy --checks='-*,lix-*' --load=build/liblix-clang-tidy.so -p ../compile_commands.json -header-filter '\.\./src/.*\.h' --fix ../src/libcmd/installables.cc
```
Several files, in parallel:
```
ninja -C build && run-clang-tidy -checks='-*,nix-*' -load=build/libnix-clang-tidy.so -p .. -fix ../src | tee -a clang-tidy-result
ninja -C build && run-clang-tidy -checks='-*,lix-*' -load=build/liblix-clang-tidy.so -p .. -header-filter '\.\./src/.*\.h' -fix ../src | tee -a clang-tidy-result
```
## Resources

View file

@ -24,7 +24,6 @@ const redirects = {
"chap-writing-nix-expressions": "language/index.html",
"part-command-ref": "command-ref/command-ref.html",
"conf-allow-import-from-derivation": "command-ref/conf-file.html#conf-allow-import-from-derivation",
"conf-allow-new-privileges": "command-ref/conf-file.html#conf-allow-new-privileges",
"conf-allowed-uris": "command-ref/conf-file.html#conf-allowed-uris",
"conf-allowed-users": "command-ref/conf-file.html#conf-allowed-users",
"conf-auto-optimise-store": "command-ref/conf-file.html#conf-auto-optimise-store",

View file

@ -0,0 +1,12 @@
---
synopsis: Enforce syscall filtering and no-new-privileges on Linux
cls: 1063
category: Breaking Changes
credits: alois31
---
In order to improve consistency of the build environment, system call filtering and no-new-privileges are now unconditionally enabled on Linux.
The `filter-syscalls` and `allow-new-privileges` options which could be used to disable these features under some circumstances have been removed.
In order to support building on architectures without libseccomp support, the option to disable syscall filtering at build time remains.
However, other uses of this option are heavily discouraged, since it would reduce the security of the sandbox substantially.

View file

@ -22,10 +22,10 @@ Migration path:
To apply this migration automatically, remove all `<nix/>` from includes, so `#include <nix/expr.hh>` -> `#include <expr.hh>`.
Then, the correct paths will be resolved from the tangled mess, and the clang-tidy automated fix will work.
Then run the following for out of tree projects:
Then run the following for out of tree projects (header filter is set to only fix instances in headers in `../src` relative to the compiler's working directory, as would be the case in nix-eval-jobs or other things built with meson, e.g.):
```console
lix_root=$HOME/lix
(cd $lix_root/clang-tidy && nix develop -c 'meson setup build && ninja -C build')
run-clang-tidy -checks='-*,lix-fixincludes' -load=$lix_root/clang-tidy/build/liblix-clang-tidy.so -p build/ -fix src
run-clang-tidy -checks='-*,lix-fixincludes' -load=$lix_root/clang-tidy/build/liblix-clang-tidy.so -p build/ -header-filter '\.\./src/.*\.h' -fix src
```

View file

@ -0,0 +1,7 @@
---
synopsis: "REPL now supports CTRL+Z to suspend"
credits: [Qyriad]
category: Improvements
---
Editline is now built with SIGTSTP support, so now typing CTRL+Z in the REPL will suspend the REPL and allow it to be resumed later or backgrounded.

View file

@ -68,10 +68,7 @@ The most current alternative to this section is to read `package.nix` and see wh
may also work, but ancient versions like the ubiquitous 2.5.4a
won't.
- The `libseccomp` is used to provide syscall filtering on Linux. This
is an optional dependency and can be disabled passing a
`--disable-seccomp-sandboxing` option to the `configure` script (Not
recommended unless your system doesn't support `libseccomp`). To get
- The `libseccomp` is used to provide syscall filtering on Linux. To get
the library, visit <https://github.com/seccomp/libseccomp>.
- On 64-bit x86 machines only, `libcpuid` library

View file

@ -24,4 +24,4 @@ install *OPTIONS: (build OPTIONS)
# Run tests
test *OPTIONS:
meson test -C build --print-errorlogs --quiet {{ OPTIONS }}
meson test -C build --print-errorlogs {{ OPTIONS }}

View file

@ -182,6 +182,9 @@ deps += cpuid
# seccomp only makes sense on Linux
seccomp_required = is_linux ? get_option('seccomp-sandboxing') : false
seccomp = dependency('libseccomp', 'seccomp', required : seccomp_required, version : '>=2.5.5')
if is_linux and not seccomp.found()
warning('Sandbox security is reduced because libseccomp has not been found! Please provide libseccomp if it supports your CPU architecture.')
endif
configdata += {
'HAVE_SECCOMP': seccomp.found().to_int(),
}

View file

@ -18,6 +18,7 @@
cmake,
curl,
doxygen,
editline-lix ? __forDefaults.editline-lix,
editline,
flex,
git,
@ -73,6 +74,10 @@
];
};
editline-lix = editline.overrideAttrs (prev: {
configureFlags = prev.configureFlags or [ ] ++ [ (lib.enableFeature true "sigstop") ];
});
lix-doc = pkgs.callPackage ./lix-doc/package.nix { };
build-release-notes = pkgs.callPackage ./maintainers/build-release-notes.nix { };
},
@ -236,7 +241,7 @@ stdenv.mkDerivation (finalAttrs: {
bzip2
xz
brotli
editline
editline-lix
openssl
sqlite
libarchive
@ -376,7 +381,7 @@ stdenv.mkDerivation (finalAttrs: {
# Export the patched version of boehmgc.
# flake.nix exports that into its overlay.
passthru = {
inherit (__forDefaults) boehmgc-nix build-release-notes;
inherit (__forDefaults) boehmgc-nix editline-lix build-release-notes;
# The collection of dependency logic for this derivation is complicated enough that
# it's easier to parameterize the devShell off an already called package.nix.

View file

@ -1115,7 +1115,7 @@ void EvalState::evalFile(const SourcePath & path_, Value & v, bool mustBeTrivial
return;
}
printTalkative("evaluating file '%1%'", resolvedPath);
debug("evaluating file '%1%'", resolvedPath);
Expr * e = nullptr;
auto j = fileParseCache.find(resolvedPath);

View file

@ -242,7 +242,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
// No need to call staticEnv.sort(), because
// args[0]->attrs is already sorted.
printTalkative("evaluating file '%1%'", path);
debug("evaluating file '%1%'", path);
Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv);
e->eval(state, *env, v);

View file

@ -133,6 +133,10 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
}
auto [storePath, input] = [&]() -> std::pair<StorePath, Input> {
// *sighs*, we print the base URL, rather than the full URL because the Nixpkgs
// fileset lib tests assume that fetching shallow and non-shallow prints exactly the
// same stderr...
printInfo("fetching %s input '%s'", this->getType(), this->toURL().base);
try {
return scheme->fetch(store, *this);
} catch (Error & e) {

View file

@ -34,7 +34,6 @@
/* Includes required for chroot support. */
#if __linux__
#include <sys/ioctl.h>
#include "linux/fchmodat2-compat.hh"
#include <net/if.h>
#include <netinet/ip.h>
#include <sys/mman.h>
@ -44,6 +43,7 @@
#include <sys/prctl.h>
#include <sys/syscall.h>
#if HAVE_SECCOMP
#include "linux/fchmodat2-compat.hh"
#include <seccomp.h>
#endif
#define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old))
@ -1612,7 +1612,6 @@ void LocalDerivationGoal::chownToBuilder(const Path & path)
void setupSeccomp()
{
#if __linux__
if (!settings.filterSyscalls) return;
#if HAVE_SECCOMP
scmp_filter_ctx ctx;
@ -1678,15 +1677,18 @@ void setupSeccomp()
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(fsetxattr), 0) != 0)
throw SysError("unable to add seccomp rule");
if (seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, settings.allowNewPrivileges ? 0 : 1) != 0)
// Set the NO_NEW_PRIVS prctl flag.
// This both makes loading seccomp filters work for unprivileged users,
// and is an additional security measure in its own right.
if (seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, 1) != 0)
throw SysError("unable to set 'no new privileges' seccomp attribute");
if (seccomp_load(ctx) != 0)
throw SysError("unable to load seccomp BPF program");
#else
throw Error(
"seccomp is not supported on this platform; "
"you can bypass this error by setting the option 'filter-syscalls' to false, but note that untrusted builds can then create setuid binaries!");
// Still set the no-new-privileges flag if libseccomp is not available.
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
throw SysError("PR_SET_NO_NEW_PRIVS failed");
#endif
#endif
}
@ -1954,10 +1956,6 @@ void LocalDerivationGoal::runChild()
throw SysError("setuid failed");
setUser = false;
// Make sure we can't possibly gain new privileges in the sandbox
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
throw SysError("PR_SET_NO_NEW_PRIVS failed");
}
#endif

View file

@ -912,29 +912,6 @@ public:
)"};
#if __linux__
Setting<bool> filterSyscalls{
this, true, "filter-syscalls",
R"(
Whether to prevent certain dangerous system calls, such as
creation of setuid/setgid files or adding ACLs or extended
attributes. Only disable this if you're aware of the
security implications.
)"};
Setting<bool> allowNewPrivileges{
this, false, "allow-new-privileges",
R"(
(Linux-specific.) By default, builders on Linux cannot acquire new
privileges by calling setuid/setgid programs or programs that have
file capabilities. For example, programs such as `sudo` or `ping`
will fail. (Note that in sandbox builds, no such programs are
available unless you bind-mount them into the sandbox via the
`sandbox-paths` option.) You can allow the use of such programs by
enabling this option. This is impure and usually undesirable, but
may be useful in certain scenarios (e.g. to spin up containers or
set up userspace network interfaces in tests).
)"};
Setting<StringSet> ignoredAcls{
this, {"security.selinux", "system.nfs4_acl", "security.csm"}, "ignored-acls",
R"(

View file

@ -20,18 +20,16 @@
#pragma once
///@file
#if HAVE_SECCOMP
# if defined(__alpha__)
# define NIX_SYSCALL_FCHMODAT2 562
# elif defined(__x86_64__) && SIZE_MAX == 0xFFFFFFFF // x32
# define NIX_SYSCALL_FCHMODAT2 1073742276
# elif defined(__mips__) && defined(__mips64) && defined(_ABIN64) // mips64/n64
# define NIX_SYSCALL_FCHMODAT2 5452
# elif defined(__mips__) && defined(__mips64) && defined(_ABIN32) // mips64/n32
# define NIX_SYSCALL_FCHMODAT2 6452
# elif defined(__mips__) && defined(_ABIO32) // mips32
# define NIX_SYSCALL_FCHMODAT2 4452
# else
# define NIX_SYSCALL_FCHMODAT2 452
# endif
#endif // HAVE_SECCOMP
#if defined(__alpha__)
# define NIX_SYSCALL_FCHMODAT2 562
#elif defined(__x86_64__) && SIZE_MAX == 0xFFFFFFFF // x32
# define NIX_SYSCALL_FCHMODAT2 1073742276
#elif defined(__mips__) && defined(__mips64) && defined(_ABIN64) // mips64/n64
# define NIX_SYSCALL_FCHMODAT2 5452
#elif defined(__mips__) && defined(__mips64) && defined(_ABIN32) // mips64/n32
# define NIX_SYSCALL_FCHMODAT2 6452
#elif defined(__mips__) && defined(_ABIO32) // mips32
# define NIX_SYSCALL_FCHMODAT2 4452
#else
# define NIX_SYSCALL_FCHMODAT2 452
#endif

View file

@ -210,7 +210,6 @@ libstore = library(
seccomp,
sqlite,
sodium,
seccomp,
curl,
openssl,
aws_sdk,

View file

@ -363,11 +363,6 @@ void mainWrapped(int argc, char * * argv)
setLogFormat("bar");
settings.verboseBuild = false;
if (isatty(STDERR_FILENO)) {
verbosity = lvlNotice;
} else {
verbosity = lvlInfo;
}
NixArgs args;

View file

@ -111,7 +111,23 @@ struct CmdUpgradeNix : MixDryRun, EvalCommand
if (pathExists(canonProfileDir + "/manifest.nix")) {
std::string nixEnvCmd = settings.nixBinDir + "/nix-env";
// {settings.nixBinDir}/nix-env is a symlink to a {settings.nixBinDir}/nix, which *then*
// is a symlink to /nix/store/meow-nix/bin/nix. We want /nix/store/meow-nix/bin/nix-env.
Path const nixInStore = canonPath(settings.nixBinDir + "/nix-env", true);
Path const nixEnvCmd = dirOf(nixInStore) + "/nix-env";
// First remove the existing Nix, then use that Nix by absolute path to
// install the new one, in case the new and old versions aren't considered
// to be "the same package" by nix-env's logic (e.g., if their pnames differ).
Strings removeArgs = {
"--uninstall",
nixEnvCmd,
"--profile",
this->profileDir,
};
printTalkative("running %s %s", nixEnvCmd, concatStringsSep(" ", removeArgs));
runProgram(nixEnvCmd, false, removeArgs);
Strings upgradeArgs = {
"--profile",
this->profileDir,

View file

@ -155,6 +155,7 @@ diff -u <(
nix --offline profile install $flake2Dir 2>&1 1> /dev/null \
| grep -vE "^warning: " \
| grep -vE "^error \(ignored\): " \
| grep -vE "^fetching .+ input" \
|| true
) <(cat << EOF
error: An existing package already provides the following file:

View file

@ -184,7 +184,9 @@ in
symlinkResolvconf = runNixOSTestFor "x86_64-linux" ./symlink-resolvconf.nix;
rootInSandbox = runNixOSTestFor "x86_64-linux" ./root-in-sandbox;
noNewPrivilegesInSandbox = runNixOSTestFor "x86_64-linux" ./no-new-privileges/sandbox.nix;
noNewPrivilegesOutsideSandbox = runNixOSTestFor "x86_64-linux" ./no-new-privileges/no-sandbox.nix;
broken-userns = runNixOSTestFor "x86_64-linux" ./broken-userns.nix;

View file

@ -0,0 +1,21 @@
let
inherit (import ../util.nix) mkNixBuildTest;
in
mkNixBuildTest {
name = "no-new-privileges-outside-sandbox";
extraMachineConfig =
{ pkgs, ... }:
{
security.wrappers.ohno = {
owner = "root";
group = "root";
capabilities = "cap_sys_nice=eip";
source = "${pkgs.libcap}/bin/getpcaps";
};
nix.settings = {
extra-sandbox-paths = [ "/run/wrappers/bin/ohno" ];
sandbox = false;
};
};
expressionFile = ./package.nix;
}

View file

@ -0,0 +1,8 @@
{ runCommand, libcap }:
runCommand "cant-get-capabilities" { nativeBuildInputs = [ libcap.out ]; } ''
if [ "$(/run/wrappers/bin/ohno 2>&1)" != "failed to inherit capabilities: Operation not permitted" ]; then
echo "Oh no! We gained capabilities!"
exit 1
fi
touch $out
''

View file

@ -0,0 +1,18 @@
let
inherit (import ../util.nix) mkNixBuildTest;
in
mkNixBuildTest {
name = "no-new-privileges-in-sandbox";
extraMachineConfig =
{ pkgs, ... }:
{
security.wrappers.ohno = {
owner = "root";
group = "root";
capabilities = "cap_sys_nice=eip";
source = "${pkgs.libcap}/bin/getpcaps";
};
nix.settings.extra-sandbox-paths = [ "/run/wrappers/bin/ohno" ];
};
expressionFile = ./package.nix;
}

View file

@ -1,15 +0,0 @@
let
inherit (import ../util.nix) mkNixBuildTest;
in mkNixBuildTest {
name = "root-in-sandbox";
extraMachineConfig = { pkgs, ... }: {
security.wrappers.ohno = {
owner = "root";
group = "root";
setuid = true;
source = "${pkgs.coreutils}/bin/whoami";
};
nix.settings.extra-sandbox-paths = ["/run/wrappers/bin"];
};
expressionFile = ./package.nix;
}

View file

@ -1,8 +0,0 @@
{ runCommand }:
runCommand "cant-get-root-in-sandbox" {} ''
if /run/wrappers/bin/ohno; then
echo "Oh no! We're root in the sandbox!"
exit 1
fi
touch $out
''