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 03e33a46d..4278fab85 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 +#include "linux/fchmodat2-compat.hh" #include #include #include @@ -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 fc3a757d3..3d0a1f0c6 100644 --- a/tests/nixos/default.nix +++ b/tests/nixos/default.nix @@ -155,7 +155,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 +#include +#include +#include +#include +#include +#include + +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 similarity index 79% rename from tests/nixos/setuid.nix rename to 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 {}; 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 {}; runCommand "foo" {} "