summary refs log tree commit diff
diff options
context:
space:
mode:
authoredef <edef@edef.eu>2023-10-04 19:16:06 +0000
committeredef <edef@edef.eu>2023-10-05 22:04:05 +0000
commit09325d24b685a3ab5507dc906ab4019696d33d59 (patch)
treef8fe2aef2c700bf0e87312125d58601f29a94348
parentad1ca74ffe049eb240b3721d0cf3c5e88682c2a7 (diff)
downloadnixpkgs-09325d24b685a3ab5507dc906ab4019696d33d59.tar
nixpkgs-09325d24b685a3ab5507dc906ab4019696d33d59.tar.gz
nixpkgs-09325d24b685a3ab5507dc906ab4019696d33d59.tar.bz2
nixpkgs-09325d24b685a3ab5507dc906ab4019696d33d59.tar.lz
nixpkgs-09325d24b685a3ab5507dc906ab4019696d33d59.tar.xz
nixpkgs-09325d24b685a3ab5507dc906ab4019696d33d59.tar.zst
nixpkgs-09325d24b685a3ab5507dc906ab4019696d33d59.zip
nixos/security/wrappers: use musl rather than glibc and explicitly unset insecure env vars
This mitigates CVE-2023-4911, crucially without a mass-rebuild.

We drop insecure environment variables explicitly, including
glibc-specific ones, since musl doesn't do this by default.

Change-Id: I591a817e6d4575243937d9ccab51c23a96bed6f9
-rw-r--r--nixos/modules/security/wrappers/default.nix23
-rw-r--r--nixos/modules/security/wrappers/wrapper.c49
-rw-r--r--nixos/modules/security/wrappers/wrapper.nix4
3 files changed, 73 insertions, 3 deletions
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index ad65f80bb2c..a8bb0650b11 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 =
diff --git a/nixos/modules/security/wrappers/wrapper.c b/nixos/modules/security/wrappers/wrapper.c
index 2cf1727a31c..cf19c7a4aa4 100644
--- a/nixos/modules/security/wrappers/wrapper.c
+++ b/nixos/modules/security/wrappers/wrapper.c
@@ -17,6 +17,9 @@
 #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
@@ -151,9 +154,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..da2fca98d5c 100644
--- a/nixos/modules/security/wrappers/wrapper.nix
+++ b/nixos/modules/security/wrappers/wrapper.nix
@@ -1,4 +1,4 @@
-{ 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 {
@@ -16,6 +16,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
   '';
 }