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
This commit is contained in:
edef 2023-10-04 19:16:06 +00:00
parent ad1ca74ffe
commit 09325d24b6
3 changed files with 73 additions and 3 deletions

View file

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

View file

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

View file

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