Merge "libstore/local-derivation-goal: prohibit creating setuid/setgid binaries" into main

This commit is contained in:
Maximilian Bosch 2024-05-04 07:26:15 +00:00 committed by Gerrit Code Review
commit 79d0ae6670
9 changed files with 121 additions and 7 deletions

View file

@ -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.

View file

@ -163,9 +163,10 @@
busybox-sandbox-shell = final.busybox-sandbox-shell or final.default-busybox-sandbox-shell; 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. # for consumers of this flake.
boehmgc-nix = final.nix.boehmgc-nix; boehmgc-nix = final.nix.boehmgc-nix;
libseccomp-nix = final.nix.libseccomp-nix;
}; };
in in
{ {

View file

@ -181,7 +181,7 @@ 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) seccomp = dependency('libseccomp', 'seccomp', required : seccomp_required, version : '>=2.5.5')
configdata += { configdata += {
'HAVE_SECCOMP': seccomp.found().to_int(), 'HAVE_SECCOMP': seccomp.found().to_int(),
} }

View file

@ -21,12 +21,14 @@
curl, curl,
doxygen, doxygen,
editline, editline,
fetchurl,
flex, flex,
git, git,
gtest, gtest,
jq, jq,
libarchive, libarchive,
libcpuid, libcpuid,
libseccomp-nix ? __forDefaults.libseccomp-nix,
libseccomp, libseccomp,
libsodium, libsodium,
lsof, lsof,
@ -82,6 +84,18 @@
}; };
lix-doc = pkgs.callPackage ./lix-doc/package.nix { }; 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 let
@ -273,7 +287,7 @@ stdenv.mkDerivation (finalAttrs: {
lix-doc lix-doc
] ]
++ lib.optionals stdenv.hostPlatform.isLinux [ ++ lib.optionals stdenv.hostPlatform.isLinux [
libseccomp libseccomp-nix
busybox-sandbox-shell busybox-sandbox-shell
] ]
++ lib.optional internalApiDocs rapidcheck ++ lib.optional internalApiDocs rapidcheck
@ -411,7 +425,9 @@ stdenv.mkDerivation (finalAttrs: {
passthru.perl-bindings = pkgs.callPackage ./perl { inherit fileset stdenv buildWithMeson; }; 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. # flake.nix exports that into its overlay.
passthru.boehmgc-nix = __forDefaults.boehmgc-nix; passthru = {
inherit (__forDefaults) boehmgc-nix libseccomp-nix;
};
}) })

View file

@ -35,6 +35,7 @@
/* 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>
@ -1664,6 +1665,10 @@ void setupSeccomp()
if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(fchmodat), 1, 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) SCMP_A2(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm)) != 0)
throw SysError("unable to add seccomp rule"); 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 /* Prevent builders from creating EAs or ACLs. Not all filesystems

View file

@ -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

View file

@ -155,7 +155,7 @@ in
setuid = lib.genAttrs setuid = lib.genAttrs
["i686-linux" "x86_64-linux"] ["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; ca-fd-leak = runNixOSTestFor "x86_64-linux" ./ca-fd-leak;

View file

@ -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);
}

View file

@ -5,6 +5,16 @@
let let
pkgs = config.nodes.machine.nixpkgs.pkgs; 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 in
{ {
name = "setuid"; name = "setuid";
@ -14,13 +24,29 @@ in
{ virtualisation.writableStore = true; { virtualisation.writableStore = true;
nix.settings.substituters = lib.mkForce [ ]; nix.settings.substituters = lib.mkForce [ ];
nix.nixPath = [ "nixpkgs=${lib.cleanSource pkgs.path}" ]; 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 }: '' testScript = { nodes }: ''
# fmt: off # fmt: off
start_all() 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. # Copying to /tmp should succeed.
machine.succeed(r""" machine.succeed(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" {} " nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" {} "