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
#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
This commit is contained in:
alois31 2024-05-08 19:15:00 +02:00 committed by alois31
parent 19ea351642
commit f047e4357b
14 changed files with 87 additions and 78 deletions

View file

@ -24,7 +24,6 @@ const redirects = {
"chap-writing-nix-expressions": "language/index.html", "chap-writing-nix-expressions": "language/index.html",
"part-command-ref": "command-ref/command-ref.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-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-uris": "command-ref/conf-file.html#conf-allowed-uris",
"conf-allowed-users": "command-ref/conf-file.html#conf-allowed-users", "conf-allowed-users": "command-ref/conf-file.html#conf-allowed-users",
"conf-auto-optimise-store": "command-ref/conf-file.html#conf-auto-optimise-store", "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

@ -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 may also work, but ancient versions like the ubiquitous 2.5.4a
won't. won't.
- The `libseccomp` is used to provide syscall filtering on Linux. This - The `libseccomp` is used to provide syscall filtering on Linux. To get
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 library, visit <https://github.com/seccomp/libseccomp>. the library, visit <https://github.com/seccomp/libseccomp>.
- On 64-bit x86 machines only, `libcpuid` library - On 64-bit x86 machines only, `libcpuid` library

View file

@ -182,6 +182,9 @@ deps += cpuid
# seccomp only makes sense on Linux # seccomp only makes sense on Linux
seccomp_required = is_linux ? get_option('seccomp-sandboxing') : false seccomp_required = is_linux ? get_option('seccomp-sandboxing') : false
seccomp = dependency('libseccomp', 'seccomp', required : seccomp_required, version : '>=2.5.5') 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 += { configdata += {
'HAVE_SECCOMP': seccomp.found().to_int(), 'HAVE_SECCOMP': seccomp.found().to_int(),
} }

View file

@ -34,7 +34,6 @@
/* Includes required for chroot support. */ /* Includes required for chroot support. */
#if __linux__ #if __linux__
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include "linux/fchmodat2-compat.hh"
#include <net/if.h> #include <net/if.h>
#include <netinet/ip.h> #include <netinet/ip.h>
#include <sys/mman.h> #include <sys/mman.h>
@ -44,6 +43,7 @@
#include <sys/prctl.h> #include <sys/prctl.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#if HAVE_SECCOMP #if HAVE_SECCOMP
#include "linux/fchmodat2-compat.hh"
#include <seccomp.h> #include <seccomp.h>
#endif #endif
#define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old)) #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() void setupSeccomp()
{ {
#if __linux__ #if __linux__
if (!settings.filterSyscalls) return;
#if HAVE_SECCOMP #if HAVE_SECCOMP
scmp_filter_ctx ctx; scmp_filter_ctx ctx;
@ -1678,15 +1677,18 @@ void setupSeccomp()
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(fsetxattr), 0) != 0) seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(fsetxattr), 0) != 0)
throw SysError("unable to add seccomp rule"); 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"); throw SysError("unable to set 'no new privileges' seccomp attribute");
if (seccomp_load(ctx) != 0) if (seccomp_load(ctx) != 0)
throw SysError("unable to load seccomp BPF program"); throw SysError("unable to load seccomp BPF program");
#else #else
throw Error( // Still set the no-new-privileges flag if libseccomp is not available.
"seccomp is not supported on this platform; " if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
"you can bypass this error by setting the option 'filter-syscalls' to false, but note that untrusted builds can then create setuid binaries!"); throw SysError("PR_SET_NO_NEW_PRIVS failed");
#endif #endif
#endif #endif
} }
@ -1954,10 +1956,6 @@ void LocalDerivationGoal::runChild()
throw SysError("setuid failed"); throw SysError("setuid failed");
setUser = false; 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 #endif

View file

@ -912,29 +912,6 @@ public:
)"}; )"};
#if __linux__ #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{ Setting<StringSet> ignoredAcls{
this, {"security.selinux", "system.nfs4_acl", "security.csm"}, "ignored-acls", this, {"security.selinux", "system.nfs4_acl", "security.csm"}, "ignored-acls",
R"( R"(

View file

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

View file

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

View file

@ -184,7 +184,9 @@ in
symlinkResolvconf = runNixOSTestFor "x86_64-linux" ./symlink-resolvconf.nix; 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; 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
''