aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/rl-next/fchmodat2-sandbox.md8
-rw-r--r--flake.nix3
-rw-r--r--meson.build2
-rw-r--r--package.nix22
-rw-r--r--src/libstore/build/local-derivation-goal.cc5
-rw-r--r--src/libstore/linux/fchmodat2-compat.hh37
-rw-r--r--tests/nixos/default.nix2
-rw-r--r--tests/nixos/setuid/fchmodat2-suid.c21
-rw-r--r--tests/nixos/setuid/setuid.nix (renamed from tests/nixos/setuid.nix)28
9 files changed, 121 insertions, 7 deletions
diff --git a/doc/manual/rl-next/fchmodat2-sandbox.md b/doc/manual/rl-next/fchmodat2-sandbox.md
new file mode 100644
index 000000000..82429a93d
--- /dev/null
+++ b/doc/manual/rl-next/fchmodat2-sandbox.md
@@ -0,0 +1,8 @@
+---
+synopsis: Creating setuid/setgid binaries with fchmodat2 is now prohibited by the build sandbox
+prs: 10501
+---
+
+The build sandbox blocks any attempt to create setuid/setgid binaries, but didn't check
+for the use of the `fchmodat2` syscall which was introduced in Linux 6.6 and is used by
+glibc >=2.39. This is fixed now.
diff --git a/flake.nix b/flake.nix
index e8526f5c4..d437cd3b8 100644
--- a/flake.nix
+++ b/flake.nix
@@ -163,9 +163,10 @@
busybox-sandbox-shell = final.busybox-sandbox-shell or final.default-busybox-sandbox-shell;
};
- # Export the patched version of boehmgc that Lix uses into the overlay
+ # Export the patched version of boehmgc & libseccomp that Lix uses into the overlay
# for consumers of this flake.
boehmgc-nix = final.nix.boehmgc-nix;
+ libseccomp-nix = final.nix.libseccomp-nix;
};
in
{
diff --git a/meson.build b/meson.build
index d40a9029a..968255ef6 100644
--- a/meson.build
+++ b/meson.build
@@ -181,7 +181,7 @@ deps += cpuid
# seccomp only makes sense on Linux
seccomp_required = is_linux ? get_option('seccomp-sandboxing') : false
-seccomp = dependency('libseccomp', 'seccomp', required : seccomp_required)
+seccomp = dependency('libseccomp', 'seccomp', required : seccomp_required, version : '>=2.5.5')
configdata += {
'HAVE_SECCOMP': seccomp.found().to_int(),
}
diff --git a/package.nix b/package.nix
index c9cc17c29..ae26d49fe 100644
--- a/package.nix
+++ b/package.nix
@@ -21,12 +21,14 @@
curl,
doxygen,
editline,
+ fetchurl,
flex,
git,
gtest,
jq,
libarchive,
libcpuid,
+ libseccomp-nix ? __forDefaults.libseccomp-nix,
libseccomp,
libsodium,
lsof,
@@ -82,6 +84,18 @@
};
lix-doc = pkgs.callPackage ./lix-doc/package.nix { };
+
+ # remove when we drop 23.11 support (which includes a version too old to know about fchmodat2)
+ # see src/libstore/linux/fchmodat2-compat.hh
+ libseccomp-nix =
+ assert lib.versionOlder (lib.getVersion libseccomp) "2.5.5";
+ libseccomp.overrideAttrs (_: rec {
+ version = "2.5.5";
+ src = fetchurl {
+ url = "https://github.com/seccomp/libseccomp/releases/download/v${version}/libseccomp-${version}.tar.gz";
+ hash = "sha256-JIosik2bmFiqa69ScSw0r+/PnJ6Ut23OAsHJqiX7M3U=";
+ };
+ });
},
}:
let
@@ -273,7 +287,7 @@ stdenv.mkDerivation (finalAttrs: {
lix-doc
]
++ lib.optionals stdenv.hostPlatform.isLinux [
- libseccomp
+ libseccomp-nix
busybox-sandbox-shell
]
++ lib.optional internalApiDocs rapidcheck
@@ -411,7 +425,9 @@ stdenv.mkDerivation (finalAttrs: {
passthru.perl-bindings = pkgs.callPackage ./perl { inherit fileset stdenv buildWithMeson; };
- # Export the patched version of boehmgc.
+ # Export the patched version of boehmgc & libseccomp.
# flake.nix exports that into its overlay.
- passthru.boehmgc-nix = __forDefaults.boehmgc-nix;
+ passthru = {
+ inherit (__forDefaults) boehmgc-nix libseccomp-nix;
+ };
})
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index 35e7ce907..248362c68 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -35,6 +35,7 @@
/* 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>
@@ -1664,6 +1665,10 @@ void setupSeccomp()
if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(fchmodat), 1,
SCMP_A2(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm)) != 0)
throw SysError("unable to add seccomp rule");
+
+ if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), NIX_SYSCALL_FCHMODAT2, 1,
+ SCMP_A2(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm)) != 0)
+ throw SysError("unable to add seccomp rule");
}
/* Prevent builders from creating EAs or ACLs. Not all filesystems
diff --git a/src/libstore/linux/fchmodat2-compat.hh b/src/libstore/linux/fchmodat2-compat.hh
new file mode 100644
index 000000000..b05da6786
--- /dev/null
+++ b/src/libstore/linux/fchmodat2-compat.hh
@@ -0,0 +1,37 @@
+/*
+ * Determine the syscall number for `fchmodat2`.
+ *
+ * On most platforms this is 452. Exceptions can be found on
+ * a glibc git checkout via `rg --pcre2 'define __NR_fchmodat2 (?!452)'`.
+ *
+ * The problem is that glibc 2.39 and libseccomp 2.5.5 are needed to
+ * get the syscall number. However, a Nix built against nixpkgs 23.11
+ * (glibc 2.38) should still have the issue fixed without depending
+ * on the build environment.
+ *
+ * To achieve that, the macros below try to determine the platform and
+ * set the syscall number which is platform-specific, but
+ * in most cases 452.
+ *
+ * TODO: remove this when 23.11 is EOL and the entire (supported) ecosystem
+ * is on glibc 2.39.
+ */
+
+#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
diff --git a/tests/nixos/default.nix b/tests/nixos/default.nix
index 3ef1217ac..f7a8588e5 100644
--- a/tests/nixos/default.nix
+++ b/tests/nixos/default.nix
@@ -153,7 +153,7 @@ in
setuid = lib.genAttrs
["i686-linux" "x86_64-linux"]
- (system: runNixOSTestFor system ./setuid.nix);
+ (system: runNixOSTestFor system ./setuid/setuid.nix);
ca-fd-leak = runNixOSTestFor "x86_64-linux" ./ca-fd-leak;
diff --git a/tests/nixos/setuid/fchmodat2-suid.c b/tests/nixos/setuid/fchmodat2-suid.c
new file mode 100644
index 000000000..931489ad7
--- /dev/null
+++ b/tests/nixos/setuid/fchmodat2-suid.c
@@ -0,0 +1,21 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+
+int main(void) {
+ char *name = getenv("out");
+ FILE *fd = fopen(name, "w");
+ fprintf(fd, "henlo :3");
+ fclose(fd);
+
+ // FIXME use something nicer here that's less
+ // platform-dependent as soon as we go to 24.05
+ // and the glibc is new enough to support fchmodat2
+ long rs = syscall(452, NULL, name, S_ISUID, 0);
+ assert(rs == -1);
+ assert(errno == EPERM);
+}
diff --git a/tests/nixos/setuid.nix b/tests/nixos/setuid/setuid.nix
index 2b66320dd..c4dc8dccb 100644
--- a/tests/nixos/setuid.nix
+++ b/tests/nixos/setuid/setuid.nix
@@ -5,6 +5,16 @@
let
pkgs = config.nodes.machine.nixpkgs.pkgs;
+ fchmodat2-builder = pkgs.runCommandCC "fchmodat2-suid" {
+ passAsFile = [ "code" ];
+ code = builtins.readFile ./fchmodat2-suid.c;
+ # Doesn't work with -O0, shuts up the warning about that.
+ hardeningDisable = [ "fortify" ];
+ } ''
+ mkdir -p $out/bin/
+ $CC -x c "$codePath" -O0 -g -o $out/bin/fchmodat2-suid
+ '';
+
in
{
name = "setuid";
@@ -14,13 +24,29 @@ in
{ virtualisation.writableStore = true;
nix.settings.substituters = lib.mkForce [ ];
nix.nixPath = [ "nixpkgs=${lib.cleanSource pkgs.path}" ];
- virtualisation.additionalPaths = [ pkgs.stdenvNoCC pkgs.pkgsi686Linux.stdenvNoCC ];
+ virtualisation.additionalPaths = [
+ pkgs.stdenvNoCC
+ pkgs.pkgsi686Linux.stdenvNoCC
+ fchmodat2-builder
+ ];
+ # need at least 6.6 to test for fchmodat2
+ boot.kernelPackages = pkgs.linuxKernel.packages.linux_6_6;
+
};
testScript = { nodes }: ''
# fmt: off
start_all()
+ with subtest("fchmodat2 suid regression test"):
+ machine.succeed("""
+ nix-build -E '(with import <nixpkgs> {}; runCommand "fchmodat2-suid" {
+ BUILDER = builtins.storePath ${fchmodat2-builder};
+ } "
+ exec \\"$BUILDER\\"/bin/fchmodat2-suid
+ ")'
+ """)
+
# Copying to /tmp should succeed.
machine.succeed(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" {} "