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:
parent
ad1ca74ffe
commit
09325d24b6
3 changed files with 73 additions and 3 deletions
|
@ -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 =
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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
|
||||
'';
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue