1
0
Fork 0
mirror of https://github.com/LnL7/nix-darwin.git synced 2025-03-31 04:04:45 +00:00

security.wrappers: copy source programs instead of wrapping in binaries

Copying the wrapper code from NixOS and hoping that I managed to catch
all the relevant dyld environment variables was not particularly
confidence-inducing, so this commit removes the wrappers entirely and
simply copies the source programs before modifying permissions. This
means that the given source programs must safely handle being
setuid/setgid binaries and being located in `/run/wrappers/bin`.

There's now only `default.nix` in `modules/security/wrappers`, so I
could have removed the directory, but I have left it for now in
anticipation of other files potentially ending up there.
This commit is contained in:
Sam 2025-01-20 01:10:15 -08:00
parent 4fc8bc106a
commit 5776f9bf3d
No known key found for this signature in database
GPG key ID: 07C4B9795517E3B4
3 changed files with 2 additions and 170 deletions

View file

@ -52,10 +52,6 @@ let
(opts: mkWrapper opts)
(builtins.attrValues cfg.wrappers);
securityWrapper = sourceProg : pkgs.pkgsStatic.callPackage ./wrapper.nix {
inherit sourceProg;
};
mkWrapper =
{ program
, source
@ -71,10 +67,10 @@ let
codesigned = if codesign
then ''
# codesign ${source} to "$wrapperDir/${program}" INSTEAD OF the next line
cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}"
cp ${source} "$wrapperDir/${program}"
''
else ''
cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}"
cp ${source} "$wrapperDir/${program}"
'';
in
''

View file

@ -1,145 +0,0 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdnoreturn.h>
#include <sys/errno.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))
extern char **environ;
// Wrapper debug variable name
static char *wrapper_debug = "WRAPPER_DEBUG";
static noreturn void assert_failure(const char *assertion) {
fprintf(stderr, "Assertion `%s` in NixOS's wrapper.c failed.\n", assertion);
fflush(stderr);
abort();
}
// The following comment and list of env vars are taken from the NixOS wrapper.
// Most of them likely have no effect on Darwin, but I have chosen to continue
// unsetting them just in case.
// 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 GLIBC_UNSECURE_ENVVARS \
"GCONV_PATH\0" \
"GETCONF_DIR\0" \
"GLIBC_TUNABLES\0" \
"HOSTALIASES\0" \
"LD_AUDIT\0" \
"LD_BIND_NOT\0" \
"LD_BIND_NOW\0" \
"LD_DEBUG\0" \
"LD_DEBUG_OUTPUT\0" \
"LD_DYNAMIC_WEAK\0" \
"LD_HWCAP_MASK\0" \
"LD_LIBRARY_PATH\0" \
"LD_ORIGIN_PATH\0" \
"LD_PRELOAD\0" \
"LD_PROFILE\0" \
"LD_SHOW_AUXV\0" \
"LD_VERBOSE\0" \
"LD_WARN\0" \
"LOCALDOMAIN\0" \
"LOCPATH\0" \
"MALLOC_ARENA_MAX\0" \
"MALLOC_ARENA_TEST\0" \
"MALLOC_CHECK\0" \
"MALLOC_MMAP_MAX_\0" \
"MALLOC_MMAP_THRESHOLD_\0" \
"MALLOC_PERTURB_\0" \
"MALLOC_TOP_PAD_\0" \
"MALLOC_TRACE\0" \
"MALLOC_TRIM_THRESHOLD_\0" \
"NIS_PATH\0" \
"NLSPATH\0" \
"RESOLV_HOST_CONF\0" \
"RES_OPTIONS\0" \
"TMPDIR\0" \
"TZDIR\0"
// These are the variables that dyld refers to to load libraries.
// Taken from `man dyld` on macOS 14.6.1.
// macOS appears to ignore these variables for setuid binaries,
// but again we err on the side of caution.
#define DYLD_UNSECURE_ENVVARS \
"DYLD_FRAMEWORK_PATH\0" \
"DYLD_FALLBACK_FRAMEWORK_PATH\0" \
"DYLD_VERSIONED_FRAMEWORK_PATH\0" \
"DYLD_LIBRARY_PATH\0" \
"DYLD_FALLBACK_LIBRARY_PATH\0" \
"DYLD_VERSIONED_LIBRARY_PATH\0" \
"DYLD_IMAGE_SUFFIX\0" \
"DYLD_INSERT_LIBRARIES\0" \
"DYLD_PRINT_TO_FILE\0" \
"DYLD_PRINT_LIBRARIES\0" \
"DYLD_PRINT_LOADERS\0" \
"DYLD_PRINT_SEARCHING\0" \
"DYLD_PRINT_APIS\0" \
"DYLD_PRINT_BINDINGS\0" \
"DYLD_PRINT_INITIALIZERS\0" \
"DYLD_PRINT_SEGMENTS\0" \
"DYLD_PRINT_ENV\0" \
"DYLD_SHARED_REGION\0" \
"DYLD_SHARED_CACHE_DIR\0" \
int main(int argc, char **argv) {
ASSERT(argc >= 1);
// argv[0] goes into a lot of places, to a far greater degree than other elements
// of argv. glibc has had buffer overflows relating to argv[0], eg CVE-2023-6246.
// Since we expect the wrappers to be invoked from either $PATH or /run/wrappers/bin,
// there should be no reason to pass any particularly large values here, so we can
// be strict for strictness' sake.
ASSERT(strlen(argv[0]) < 512);
int debug = getenv(wrapper_debug) != NULL;
// Drop insecure environment variables explicitly
//
// dyld does this automatically in SUID binaries (for everything in DYLD_ENVVARS),
// but we'd like to cover this:
//
// a) for variables that dyld does not unset (such as TMPDIR)
// b) in binaries with entitlements, but don't run with an altered effective
// UID/GID, and thus don't have env vars stripped automatically
//
// We're using musl, which doesn't drop environment variables in secure mode,
// and we'd also like dyld-specific variables to be covered.
//
// If we don't explicitly unset them, it's quite easy to just set DYLD_INSERT_LIBRARIES,
// have it passed through to the wrapped program, and gain privileges.
for (char *unsec = GLIBC_UNSECURE_ENVVARS DYLD_UNSECURE_ENVVARS; *unsec; unsec = strchr(unsec, 0) + 1) {
if (debug) {
fprintf(stderr, "unsetting %s\n", unsec);
}
unsetenv(unsec);
}
execve(SOURCE_PROG, argv, environ);
fprintf(stderr, "%s: cannot run `%s': %s\n",
argv[0], SOURCE_PROG, strerror(errno));
return 1;
}

View file

@ -1,19 +0,0 @@
{ stdenv, sourceProg, debug ? false }:
# For testing:
# $ nix-build -E 'with import <nixpkgs> {}; pkgs.callPackage ./wrapper.nix { sourceProg = "${pkgs.hello}/bin/hello"; debug = true; }'
stdenv.mkDerivation {
name = "security-wrapper-${baseNameOf sourceProg}";
dontUnpack = true;
CFLAGS = [
''-DSOURCE_PROG="${sourceProg}"''
] ++ (if debug then [
"-Werror" "-Og" "-g"
] else [
"-Wall" "-O2"
]);
dontStrip = debug;
installPhase = ''
mkdir -p $out/bin
$CC $CFLAGS ${./wrapper.c} -o $out/bin/security-wrapper
'';
}