diff options
Diffstat (limited to 'nixos/modules/security/wrappers')
-rw-r--r-- | nixos/modules/security/wrappers/default.nix | 80 | ||||
-rw-r--r-- | nixos/modules/security/wrappers/wrapper.c | 57 | ||||
-rw-r--r-- | nixos/modules/security/wrappers/wrapper.nix | 5 |
3 files changed, 104 insertions, 38 deletions
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix index ad65f80bb2c..250f9775be1 100644 --- a/nixos/modules/security/wrappers/default.nix +++ b/nixos/modules/security/wrappers/default.nix @@ -5,8 +5,29 @@ let parentWrapperDir = dirOf wrapperDir; - securityWrapper = sourceProg : pkgs.callPackage ./wrapper.nix { + # This is security-sensitive code, and glibc vulns happen from time to time. + # musl is security-focused and generally more minimal, so it's a better choice here. + # The dynamic linker is still a fairly complex piece of code, and the wrappers are + # quite small, so linking it statically is more appropriate. + securityWrapper = sourceProg : pkgs.pkgsStatic.callPackage ./wrapper.nix { inherit sourceProg; + + # glibc definitions of insecure environment variables + # + # We extract the single header file we need into its own derivation, + # so that we don't have to pull full glibc sources to build wrappers. + # + # They're taken from pkgs.glibc so that we don't have to keep as close + # an eye on glibc changes. Not every relevant variable is in this header, + # so we maintain a slightly stricter list in wrapper.c itself as well. + unsecvars = lib.overrideDerivation (pkgs.srcOnly pkgs.glibc) + ({ name, ... }: { + name = "${name}-unsecvars"; + installPhase = '' + mkdir $out + cp sysdeps/generic/unsecvars.h $out + ''; + }); }; fileModeType = @@ -254,33 +275,38 @@ in mrpx ${wrap.source}, '') wrappers; - ###### wrappers activation script - system.activationScripts.wrappers = - lib.stringAfter [ "specialfs" "users" ] - '' - chmod 755 "${parentWrapperDir}" - - # We want to place the tmpdirs for the wrappers to the parent dir. - wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX) - chmod a+rx "$wrapperDir" - - ${lib.concatStringsSep "\n" mkWrappedPrograms} - - if [ -L ${wrapperDir} ]; then - # Atomically replace the symlink - # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/ - old=$(readlink -f ${wrapperDir}) - if [ -e "${wrapperDir}-tmp" ]; then - rm --force --recursive "${wrapperDir}-tmp" - fi - ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp" - mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}" - rm --force --recursive "$old" - else - # For initial setup - ln --symbolic "$wrapperDir" "${wrapperDir}" + systemd.services.suid-sgid-wrappers = { + description = "Create SUID/SGID Wrappers"; + wantedBy = [ "sysinit.target" ]; + before = [ "sysinit.target" ]; + unitConfig.DefaultDependencies = false; + unitConfig.RequiresMountsFor = [ "/nix/store" "/run/wrappers" ]; + serviceConfig.Type = "oneshot"; + script = '' + chmod 755 "${parentWrapperDir}" + + # We want to place the tmpdirs for the wrappers to the parent dir. + wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX) + chmod a+rx "$wrapperDir" + + ${lib.concatStringsSep "\n" mkWrappedPrograms} + + if [ -L ${wrapperDir} ]; then + # Atomically replace the symlink + # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/ + old=$(readlink -f ${wrapperDir}) + if [ -e "${wrapperDir}-tmp" ]; then + rm --force --recursive "${wrapperDir}-tmp" fi - ''; + ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp" + mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}" + rm --force --recursive "$old" + else + # For initial setup + ln --symbolic "$wrapperDir" "${wrapperDir}" + fi + ''; + }; ###### wrappers consistency checks system.checks = lib.singleton (pkgs.runCommandLocal diff --git a/nixos/modules/security/wrappers/wrapper.c b/nixos/modules/security/wrappers/wrapper.c index 2cf1727a31c..3277e7ef6f7 100644 --- a/nixos/modules/security/wrappers/wrapper.c +++ b/nixos/modules/security/wrappers/wrapper.c @@ -17,14 +17,15 @@ #include <syscall.h> #include <byteswap.h> +// imported from glibc +#include "unsecvars.h" + #ifndef SOURCE_PROG #error SOURCE_PROG should be defined via preprocessor commandline #endif // aborts when false, printing the failed expression #define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr)) -// aborts when returns non-zero, printing the failed expression and errno -#define MUSTSUCCEED(expr) ((expr) ? print_errno_and_die(#expr) : (void) 0) extern char **environ; @@ -45,12 +46,6 @@ static noreturn void assert_failure(const char *assertion) { abort(); } -static noreturn void print_errno_and_die(const char *assertion) { - fprintf(stderr, "Call `%s` in NixOS's wrapper.c failed: %s\n", assertion, strerror(errno)); - fflush(stderr); - abort(); -} - int get_last_cap(unsigned *last_cap) { FILE* file = fopen("/proc/sys/kernel/cap_last_cap", "r"); if (file == NULL) { @@ -151,9 +146,55 @@ static int make_caps_ambient(const char *self_path) { return 0; } +// These are environment variable aliases for glibc tunables. +// This list shouldn't grow further, since this is a legacy mechanism. +// Any future tunables are expected to only be accessible through GLIBC_TUNABLES. +// +// They are not included in the glibc-provided UNSECURE_ENVVARS list, +// since any SUID executable ignores them. This wrapper also serves +// executables that are merely granted ambient capabilities, rather than +// being SUID, and hence don't run in secure mode. We'd like them to +// defend those in depth as well, so we clear these explicitly. +// +// Except for MALLOC_CHECK_ (which is marked SXID_ERASE), these are all +// marked SXID_IGNORE (ignored in secure mode), so even the glibc version +// of this wrapper would leave them intact. +#define UNSECURE_ENVVARS_TUNABLES \ + "MALLOC_CHECK_\0" \ + "MALLOC_TOP_PAD_\0" \ + "MALLOC_PERTURB_\0" \ + "MALLOC_MMAP_THRESHOLD_\0" \ + "MALLOC_TRIM_THRESHOLD_\0" \ + "MALLOC_MMAP_MAX_\0" \ + "MALLOC_ARENA_MAX\0" \ + "MALLOC_ARENA_TEST\0" + int main(int argc, char **argv) { ASSERT(argc >= 1); + int debug = getenv(wrapper_debug) != NULL; + + // Drop insecure environment variables explicitly + // + // glibc does this automatically in SUID binaries, but we'd like to cover this: + // + // a) before it gets to glibc + // b) in binaries that are only granted ambient capabilities by the wrapper, + // but don't run with an altered effective UID/GID, nor directly gain + // capabilities themselves, and thus don't run in secure mode. + // + // We're using musl, which doesn't drop environment variables in secure mode, + // and we'd also like glibc-specific variables to be covered. + // + // If we don't explicitly unset them, it's quite easy to just set LD_PRELOAD, + // have it passed through to the wrapped program, and gain privileges. + for (char *unsec = UNSECURE_ENVVARS_TUNABLES UNSECURE_ENVVARS; *unsec; unsec = strchr(unsec, 0) + 1) { + if (debug) { + fprintf(stderr, "unsetting %s\n", unsec); + } + unsetenv(unsec); + } + // Read the capabilities set on the wrapper and raise them in to // the ambient set so the program we're wrapping receives the // capabilities too! diff --git a/nixos/modules/security/wrappers/wrapper.nix b/nixos/modules/security/wrappers/wrapper.nix index aec43412404..27d46c630af 100644 --- a/nixos/modules/security/wrappers/wrapper.nix +++ b/nixos/modules/security/wrappers/wrapper.nix @@ -1,11 +1,10 @@ -{ stdenv, linuxHeaders, sourceProg, debug ? false }: +{ stdenv, unsecvars, linuxHeaders, sourceProg, debug ? false }: # For testing: # $ nix-build -E 'with import <nixpkgs> {}; pkgs.callPackage ./wrapper.nix { parentWrapperDir = "/run/wrappers"; debug = true; }' stdenv.mkDerivation { name = "security-wrapper"; buildInputs = [ linuxHeaders ]; dontUnpack = true; - hardeningEnable = [ "pie" ]; CFLAGS = [ ''-DSOURCE_PROG="${sourceProg}"'' ] ++ (if debug then [ @@ -16,6 +15,6 @@ stdenv.mkDerivation { dontStrip = debug; installPhase = '' mkdir -p $out/bin - $CC $CFLAGS ${./wrapper.c} -o $out/bin/security-wrapper + $CC $CFLAGS ${./wrapper.c} -I${unsecvars} -o $out/bin/security-wrapper ''; } |