From 79e81aa31bc7a0fa88507c06f21b41fbbd1cb863 Mon Sep 17 00:00:00 2001 From: Parnell Springmeyer Date: Fri, 15 Jul 2016 18:05:28 -0500 Subject: [PATCH] security: Removing the old wrappers and replacing with 'permissions-wrappers' --- .../security/permissions-wrappers/default.nix | 201 ++++++++++++++++++ .../permissions-wrapper.c} | 24 ++- .../setcap-wrapper-drv.nix | 37 ++++ .../setcap-wrappers.nix | 0 .../setuid-wrapper-drv.nix | 36 ++++ .../setuid-wrappers.nix | 0 nixos/modules/security/setuid-wrapper.c | 81 ------- 7 files changed, 293 insertions(+), 86 deletions(-) create mode 100644 nixos/modules/security/permissions-wrappers/default.nix rename nixos/modules/security/{setcap-wrapper.c => permissions-wrappers/permissions-wrapper.c} (95%) create mode 100644 nixos/modules/security/permissions-wrappers/setcap-wrapper-drv.nix rename nixos/modules/security/{ => permissions-wrappers}/setcap-wrappers.nix (100%) create mode 100644 nixos/modules/security/permissions-wrappers/setuid-wrapper-drv.nix rename nixos/modules/security/{ => permissions-wrappers}/setuid-wrappers.nix (100%) delete mode 100644 nixos/modules/security/setuid-wrapper.c diff --git a/nixos/modules/security/permissions-wrappers/default.nix b/nixos/modules/security/permissions-wrappers/default.nix new file mode 100644 index 000000000000..a4491946df5d --- /dev/null +++ b/nixos/modules/security/permissions-wrappers/default.nix @@ -0,0 +1,201 @@ +{ config, lib, pkgs, ... }: +let + + inherit (config.security) permissionsWrapperDir; + + cfg = config.security.permissionsWrappers; + + setcapWrappers = import ./setcap-wrapper-drv.nix { }; + setuidWrappers = import ./setuid-wrapper-drv.nix { }; + + ###### Activation script for the setcap wrappers + configureSetcapWrapper = + { program + , capabilities + , source ? null + , owner ? "nobody" + , group ? "nogroup" + , setcap ? false + }: + '' + cp ${setcapWrappers}/bin/${program}.wrapper ${permissionsWrapperDir}/${program} + + # Prevent races + chmod 0000 ${permissionsWrapperDir}/${program} + chown ${owner}.${group} ${permissionsWrapperDir}/${program} + + # Set desired capabilities on the file plus cap_setpcap so + # the wrapper program can elevate the capabilities set on + # its file into the Ambient set. + # + # Only set the capabilities though if we're being told to + # do so. + ${ + if setcap then + '' + ${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" ${permissionsWrapperDir}/${program} + '' + else "" + } + + # Set the executable bit + chmod u+rx,g+x,o+x ${permissionsWrapperDir}/${program} + ''; + + ###### Activation script for the setuid wrappers + setuidPrograms = + (map (x: { program = x; owner = "root"; group = "root"; setuid = true; }) + config.security.setuidPrograms) + ++ config.security.setuidOwners; + + makeSetuidWrapper = + { program + , source ? null + , owner ? "nobody" + , group ? "nogroup" + , setuid ? false + , setgid ? false + , permissions ? "u+rx,g+x,o+x" + }: + + '' + cp ${setuidWrappers}/bin/${program}.wrapper ${permissionsWrapperDir}/${program} + + # Prevent races + chmod 0000 ${permissionsWrapperDir}/${program} + chown ${owner}.${group} ${permissionsWrapperDir}/${program} + + chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" ${permissionsWrapperDir}/${program} + ''; +in +{ + + ###### interface + + options = { + security.permissionsWrappers.setcap = mkOption { + type = types.listOf types.attrs; + default = []; + example = + [ { program = "ping"; + source = "${pkgs.iputils.out}/bin/ping" + owner = "nobody"; + group = "nogroup"; + setcap = true; + capabilities = "cap_net_raw+ep"; + } + ]; + description = '' + This option sets capabilities on a wrapper program that + propagates those capabilities down to the wrapped, real + program. + + The `program` attribute is the name of the program to be + wrapped. If no `source` attribute is provided, specifying the + absolute path to the program, then the program will be + searched for in the path environment variable. + + NOTE: cap_setpcap, which is required for the wrapper program + to be able to raise caps into the Ambient set is NOT raised to + the Ambient set so that the real program cannot modify its own + capabilities!! This may be too restrictive for cases in which + the real program needs cap_setpcap but it at least leans on + the side security paranoid vs. too relaxed. + + The attribute `setcap` defaults to false and it will create a + wrapper program but never set the capability set on it. This + is done so that you can remove a capability sent entirely from + a wrapper program without also needing to go change any + absolute paths that may be directly referencing the wrapper + program. + ''; + }; + + security.permissionsWrappers.setuid = mkOption { + type = types.listOf types.attrs; + default = []; + example = + [ { program = "sendmail"; + source = "${pkgs.sendmail.bin}/bin/sendmail"; + owner = "nobody"; + group = "postdrop"; + setuid = false; + setgid = true; + permissions = "u+rx,g+x,o+x"; + } + ]; + description = '' + This option allows the ownership and permissions on the setuid + wrappers for specific programs to be overridden from the + default (setuid root, but not setgid root). + ''; + }; + + security.permissionsWrapperDir = mkOption { + type = types.path; + default = "/var/permissions-wrappers"; + internal = true; + description = '' + This option defines the path to the permissions wrappers. It + should not be overriden. + ''; + }; + + }; + + + ###### implementation + + config = { + + # Make sure our setcap-wrapper dir exports to the PATH env + # variable when initializing the shell + environment.extraInit = '' + # The permissions wrappers override other bin directories. + export PATH="${config.security.permissionsWrapperDir}:$PATH" + ''; + + ###### setcap activation script + system.activationScripts.setcap = + stringAfter [ "users" ] + '' + # Look in the system path and in the default profile for + # programs to be wrapped. + PERMISSIONS_WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin + + # When a program is removed from the security.permissionsWrappers.setcap + # list we have to remove all of the previous program wrappers + # and re-build them minus the wrapper for the program removed, + # hence the rm here in the activation script. + + rm -f ${permissionsWrapperDir}/* + + # Concatenate the generated shell slices to configure + # wrappers for each program needing specialized capabilities. + + ${concatMapStrings configureSetcapWrapper cfg.setcap} + ''; + + ###### setuid activation script + system.activationScripts.setuid = + stringAfter [ "users" ] + '' + # Look in the system path and in the default profile for + # programs to be wrapped. + PERMISSIONS_WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin + + # When a program is removed from the security.permissionsWrappers.setcap + # list we have to remove all of the previous program wrappers + # and re-build them minus the wrapper for the program removed, + # hence the rm here in the activation script. + + rm -f ${permissionsWrapperDir}/* + + # Concatenate the generated shell slices to configure + # wrappers for each program needing specialized capabilities. + + ${concatMapStrings configureSetuidWrapper cfg.setuid} + ''; + + }; +} diff --git a/nixos/modules/security/setcap-wrapper.c b/nixos/modules/security/permissions-wrappers/permissions-wrapper.c similarity index 95% rename from nixos/modules/security/setcap-wrapper.c rename to nixos/modules/security/permissions-wrappers/permissions-wrapper.c index a44d174d90f7..effdaa930963 100644 --- a/nixos/modules/security/setcap-wrapper.c +++ b/nixos/modules/security/permissions-wrappers/permissions-wrapper.c @@ -8,11 +8,6 @@ #include #include #include -#include -#include -#include -#include -#include // Make sure assertions are not compiled out, we use them to codify // invariants about this program and we want it to fail fast and @@ -26,6 +21,24 @@ extern char **environ; static char * sourceProg = SOURCE_PROG; static char * wrapperDir = WRAPPER_DIR; +// Make sure we have the WRAPPER_TYPE macro specified at compile +// time... +#ifdef WRAPPER_SETCAP +static char * wrapperType = "setcap"; +#elif defined WRAPPER_SETUID +static char * wrapperType = "setuid"; +#else +fprintf(stderr, "Program must be compiled with either the WRAPPER_SETCAP or WRAPPER_SETUID macros specified!\n"); +exit(1); +#endif + +#ifdef WRAPPER_SETCAP +#include +#include +#include +#include +#include + // Update the capabilities of the running process to include the given // capability in the Ambient set. static void set_ambient_cap(cap_value_t cap) @@ -150,6 +163,7 @@ static int make_caps_ambient(const char *selfPath) return 0; } +#endif int main(int argc, char * * argv) { diff --git a/nixos/modules/security/permissions-wrappers/setcap-wrapper-drv.nix b/nixos/modules/security/permissions-wrappers/setcap-wrapper-drv.nix new file mode 100644 index 000000000000..f64c683f6e84 --- /dev/null +++ b/nixos/modules/security/permissions-wrappers/setcap-wrapper-drv.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.security.permissionsWrappers; + + # Produce a shell-code splice intended to be stitched into one of + # the build or install phases within the derivation. + mkSetcapWrapper = { program, source ? null, ...}: + '' + if ! source=${if source != null then source else "$(readlink -f $(PATH=$PERMISSIONS_WRAPPER_PATH type -tP ${program}))"}; then + # If we can't find the program, fall back to the + # system profile. + source=/nix/var/nix/profiles/default/bin/${program} + fi + + gcc -Wall -O2 -DWRAPPER_SETCAP=1 -DSOURCE_PROG=\"$source\" -DWRAPPER_DIR=\"${cfg.permissionsWrapperDir}\" \ + -lcap-ng -lcap ${./permissions-wrapper.c} -o $out/bin/${program}.wrapper + ''; +in + +# This is only useful for Linux platforms and a kernel version of +# 4.3 or greater +assert pkgs.stdenv.isLinux; +assert lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.3"; + +pkgs.stdenv.mkDerivation { + name = "setcap-wrapper"; + unpackPhase = "true"; + buildInputs = [ pkgs.linuxHeaders pkgs.libcap pkgs.libcap_ng ]; + installPhase = '' + mkdir -p $out/bin + + # Concat together all of our shell splices to compile + # binary wrapper programs for all configured setcap programs. + ${concatMapStrings mkSetcapWrapper cfg.setcap} + ''; +}; diff --git a/nixos/modules/security/setcap-wrappers.nix b/nixos/modules/security/permissions-wrappers/setcap-wrappers.nix similarity index 100% rename from nixos/modules/security/setcap-wrappers.nix rename to nixos/modules/security/permissions-wrappers/setcap-wrappers.nix diff --git a/nixos/modules/security/permissions-wrappers/setuid-wrapper-drv.nix b/nixos/modules/security/permissions-wrappers/setuid-wrapper-drv.nix new file mode 100644 index 000000000000..15dc1918b5c5 --- /dev/null +++ b/nixos/modules/security/permissions-wrappers/setuid-wrapper-drv.nix @@ -0,0 +1,36 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.security.permissionsWrappers; + + # Produce a shell-code splice intended to be stitched into one of + # the build or install phases within the derivation. + mkSetuidWrapper = { program, source ? null, ...}: + '' + if ! source=${if source != null then source else "$(readlink -f $(PATH=$PERMISSIONS_WRAPPER_PATH type -tP ${program}))"}; then + # If we can't find the program, fall back to the + # system profile. + source=/nix/var/nix/profiles/default/bin/${program} + fi + + gcc -Wall -O2 -DWRAPPER_SETUID=1 -DSOURCE_PROG=\"$source\" -DWRAPPER_DIR=\"${cfg.permissionsWrapperDir}\" \ + -lcap-ng -lcap ${./permissions-wrapper.c} -o $out/bin/${program}.wrapper + ''; +in + +# This is only useful for Linux platforms and a kernel version of +# 4.3 or greater +assert pkgs.stdenv.isLinux; +assert lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.3"; + +pkgs.stdenv.mkDerivation { + name = "setuid-wrapper"; + unpackPhase = "true"; + installPhase = '' + mkdir -p $out/bin + + # Concat together all of our shell splices to compile + # binary wrapper programs for all configured setcap programs. + ${concatMapStrings mkSetuidWrapper cfg.setuid} + ''; +}; diff --git a/nixos/modules/security/setuid-wrappers.nix b/nixos/modules/security/permissions-wrappers/setuid-wrappers.nix similarity index 100% rename from nixos/modules/security/setuid-wrappers.nix rename to nixos/modules/security/permissions-wrappers/setuid-wrappers.nix diff --git a/nixos/modules/security/setuid-wrapper.c b/nixos/modules/security/setuid-wrapper.c deleted file mode 100644 index ffd0b65b7629..000000000000 --- a/nixos/modules/security/setuid-wrapper.c +++ /dev/null @@ -1,81 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Make sure assertions are not compiled out. */ -#undef NDEBUG - -extern char **environ; - -static char * wrapperDir = WRAPPER_DIR; - -int main(int argc, char * * argv) -{ - char self[PATH_MAX]; - - int len = readlink("/proc/self/exe", self, sizeof(self) - 1); - assert (len > 0); - self[len] = 0; - - /* Make sure that we are being executed from the right location, - i.e., `wrapperDir'. This is to prevent someone from - creating hard link `X' from some other location, along with a - false `X.real' file, to allow arbitrary programs from being - executed setuid. */ - assert ((strncmp(self, wrapperDir, strlen(wrapperDir)) == 0) && - (self[strlen(wrapperDir)] == '/')); - - /* Make *really* *really* sure that we were executed as `self', - and not, say, as some other setuid program. That is, our - effective uid/gid should match the uid/gid of `self'. */ - //printf("%d %d\n", geteuid(), getegid()); - - struct stat st; - assert (lstat(self, &st) != -1); - - //printf("%d %d\n", st.st_uid, st.st_gid); - - assert ((st.st_mode & S_ISUID) == 0 || - (st.st_uid == geteuid())); - - assert ((st.st_mode & S_ISGID) == 0 || - st.st_gid == getegid()); - - /* And, of course, we shouldn't be writable. */ - assert (!(st.st_mode & (S_IWGRP | S_IWOTH))); - - - /* Read the path of the real (wrapped) program from .real. */ - char realFN[PATH_MAX + 10]; - int realFNSize = snprintf (realFN, sizeof(realFN), "%s.real", self); - assert (realFNSize < sizeof(realFN)); - - int fdSelf = open(realFN, O_RDONLY); - assert (fdSelf != -1); - - char real[PATH_MAX]; - len = read(fdSelf, real, PATH_MAX); - assert (len != -1); - assert (len < sizeof (real)); - assert (len > 0); - real[len] = 0; - - close(fdSelf); - - //printf("real = %s, len = %d\n", real, len); - - execve(real, argv, environ); - - fprintf(stderr, "%s: cannot run `%s': %s\n", - argv[0], real, strerror(errno)); - - exit(1); -}