Merge "libstore/local-derivation-goal: prohibit creating setuid/setgid binaries" into main
This commit is contained in:
commit
79d0ae6670
8
doc/manual/rl-next/fchmodat2-sandbox.md
Normal file
8
doc/manual/rl-next/fchmodat2-sandbox.md
Normal 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.
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
22
package.nix
22
package.nix
|
@ -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;
|
||||||
|
};
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
|
37
src/libstore/linux/fchmodat2-compat.hh
Normal file
37
src/libstore/linux/fchmodat2-compat.hh
Normal 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
|
|
@ -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;
|
||||||
|
|
||||||
|
|
21
tests/nixos/setuid/fchmodat2-suid.c
Normal file
21
tests/nixos/setuid/fchmodat2-suid.c
Normal 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);
|
||||||
|
}
|
|
@ -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" {} "
|
Loading…
Reference in a new issue