From e92b8402b05f34072a20075ed54660e7a7237cc3 Mon Sep 17 00:00:00 2001 From: Parnell Springmeyer Date: Sat, 28 Jan 2017 20:48:03 -0800 Subject: Addressing PR feedback --- nixos/doc/manual/release-notes/rl-1609.xml | 2 +- nixos/doc/manual/release-notes/rl-1703.xml | 8 + nixos/modules/config/shells-environment.nix | 4 +- .../installer/tools/nixos-generate-config.pl | 2 +- nixos/modules/installer/tools/nixos-install.sh | 7 +- nixos/modules/module-list.nix | 2 +- nixos/modules/programs/kbdlight.nix | 9 +- nixos/modules/programs/light.nix | 9 +- nixos/modules/programs/shadow.nix | 48 +---- nixos/modules/security/apparmor-suid.nix | 4 +- nixos/modules/security/chromium-suid-sandbox.nix | 6 +- nixos/modules/security/duosec.nix | 11 +- nixos/modules/security/pam.nix | 35 +--- nixos/modules/security/pam_usb.nix | 17 +- .../security/permissions-wrappers/default.nix | 191 ------------------ .../permissions-wrappers/permissions-wrapper.c | 221 --------------------- .../permissions-wrappers/setcap-wrapper-drv.nix | 37 ---- .../permissions-wrappers/setuid-wrapper-drv.nix | 35 ---- nixos/modules/security/polkit.nix | 18 +- nixos/modules/security/sudo.nix | 17 +- nixos/modules/security/wrappers/default.nix | 191 ++++++++++++++++++ .../security/wrappers/permissions-wrapper.c | 221 +++++++++++++++++++++ .../security/wrappers/setcap-wrapper-drv.nix | 37 ++++ .../security/wrappers/setuid-wrapper-drv.nix | 35 ++++ nixos/modules/services/logging/logcheck.nix | 4 +- nixos/modules/services/mail/dovecot.nix | 2 +- nixos/modules/services/mail/exim.nix | 12 +- nixos/modules/services/mail/mail.nix | 2 +- nixos/modules/services/monitoring/munin.nix | 4 +- nixos/modules/services/monitoring/smartd.nix | 2 +- .../modules/services/network-filesystems/samba.nix | 2 +- nixos/modules/services/networking/gale.nix | 2 +- nixos/modules/services/networking/prayer.nix | 2 +- nixos/modules/services/networking/smokeping.nix | 20 +- nixos/modules/services/scheduling/atd.nix | 2 +- nixos/modules/services/scheduling/cron.nix | 18 +- nixos/modules/services/scheduling/fcron.nix | 13 +- nixos/modules/services/system/dbus.nix | 4 +- .../x11/desktop-managers/enlightenment.nix | 11 +- .../modules/services/x11/desktop-managers/kde4.nix | 2 +- .../modules/services/x11/desktop-managers/kde5.nix | 2 +- nixos/modules/system/boot/stage-2-init.sh | 8 +- nixos/modules/tasks/network-interfaces.nix | 47 ++--- nixos/modules/virtualisation/virtualbox-host.nix | 4 +- nixos/tests/smokeping.nix | 2 +- 45 files changed, 571 insertions(+), 761 deletions(-) delete mode 100644 nixos/modules/security/permissions-wrappers/default.nix delete mode 100644 nixos/modules/security/permissions-wrappers/permissions-wrapper.c delete mode 100644 nixos/modules/security/permissions-wrappers/setcap-wrapper-drv.nix delete mode 100644 nixos/modules/security/permissions-wrappers/setuid-wrapper-drv.nix create mode 100644 nixos/modules/security/wrappers/default.nix create mode 100644 nixos/modules/security/wrappers/permissions-wrapper.c create mode 100644 nixos/modules/security/wrappers/setcap-wrapper-drv.nix create mode 100644 nixos/modules/security/wrappers/setuid-wrapper-drv.nix (limited to 'nixos') diff --git a/nixos/doc/manual/release-notes/rl-1609.xml b/nixos/doc/manual/release-notes/rl-1609.xml index bf8be1b68f26..ade7d5581ced 100644 --- a/nixos/doc/manual/release-notes/rl-1609.xml +++ b/nixos/doc/manual/release-notes/rl-1609.xml @@ -202,7 +202,7 @@ following incompatible changes: The directory container setuid wrapper programs, - /var/permissions-wrappers, /var/setuid-wrappers, is now updated atomically to prevent failures if the switch to a new configuration is interrupted. diff --git a/nixos/doc/manual/release-notes/rl-1703.xml b/nixos/doc/manual/release-notes/rl-1703.xml index 177010e2a322..94aa674fed6c 100644 --- a/nixos/doc/manual/release-notes/rl-1703.xml +++ b/nixos/doc/manual/release-notes/rl-1703.xml @@ -15,6 +15,14 @@ has the following highlights: xlink:href="https://nixos.org/nixpkgs/manual/#sec-overlays-install">Nixpkgs manual for more information. + + + + Setting capabilities on programs is now supported with a + setcap-wrapper functionality. This + functionality and the setuid-wrapper are merged + into a single "wrappers" module. + The following new services were added since the last release: diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix index 7003c0745224..8a7b3ea0bfd2 100644 --- a/nixos/modules/config/shells-environment.nix +++ b/nixos/modules/config/shells-environment.nix @@ -168,8 +168,8 @@ in ${cfg.extraInit} - # The setuid wrappers override other bin directories. - export PATH="${config.security.permissionsWrapperDir}:$PATH" + # The setuid/setcap wrappers override other bin directories. + export PATH="${config.security.wrapperDir}:$PATH" # ~/bin if it exists overrides other bin directories. export PATH="$HOME/bin:$PATH" diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl index bb379dafc642..657c28f095dd 100644 --- a/nixos/modules/installer/tools/nixos-generate-config.pl +++ b/nixos/modules/installer/tools/nixos-generate-config.pl @@ -343,7 +343,7 @@ foreach my $fs (read_file("/proc/self/mountinfo")) { # Skip special filesystems. next if in($mountPoint, "/proc") || in($mountPoint, "/dev") || in($mountPoint, "/sys") || in($mountPoint, "/run") || $mountPoint eq "/var/lib/nfs/rpc_pipefs"; - next if $mountPoint eq "/var/permissions-wrappers"; + next if $mountPoint eq "/run/wrappers"; # Skip the optional fields. my $n = 6; $n++ while $fields[$n] ne "-"; $n++; diff --git a/nixos/modules/installer/tools/nixos-install.sh b/nixos/modules/installer/tools/nixos-install.sh index 4e9f8ab60f2c..36b1a47956d6 100644 --- a/nixos/modules/installer/tools/nixos-install.sh +++ b/nixos/modules/installer/tools/nixos-install.sh @@ -92,14 +92,13 @@ fi mkdir -m 0755 -p $mountPoint/dev $mountPoint/proc $mountPoint/sys $mountPoint/etc $mountPoint/run $mountPoint/home mkdir -m 01777 -p $mountPoint/tmp mkdir -m 0755 -p $mountPoint/tmp/root -mkdir -m 0755 -p $mountPoint/var/permissions-wrappers +mkdir -m 0755 -p $mountPoint/var mkdir -m 0700 -p $mountPoint/root mount --rbind /dev $mountPoint/dev mount --rbind /proc $mountPoint/proc mount --rbind /sys $mountPoint/sys mount --rbind / $mountPoint/tmp/root mount -t tmpfs -o "mode=0755" none $mountPoint/run -mount -t tmpfs -o "mode=0755" none $mountPoint/var/permissions-wrappers rm -rf $mountPoint/var/run ln -s /run $mountPoint/var/run for f in /etc/resolv.conf /etc/hosts; do rm -f $mountPoint/$f; [ -f "$f" ] && cp -Lf $f $mountPoint/etc/; done @@ -260,9 +259,9 @@ chroot $mountPoint /nix/var/nix/profiles/system/activate # Ask the user to set a root password. -if [ -z "$noRootPasswd" ] && chroot $mountPoint [ -x /var/permissions-wrappers/passwd ] && [ -t 0 ]; then +if [ -z "$noRootPasswd" ] && chroot $mountPoint [ -x /run/wrappers/passwd ] && [ -t 0 ]; then echo "setting root password..." - chroot $mountPoint /var/permissions-wrappers/passwd + chroot $mountPoint /run/wrappers/passwd fi diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index f7206ea931b4..bd351460a528 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -113,7 +113,7 @@ ./security/prey.nix ./security/rngd.nix ./security/rtkit.nix - ./security/permissions-wrappers + ./security/wrappers ./security/sudo.nix ./services/amqp/activemq/default.nix ./services/amqp/rabbitmq.nix diff --git a/nixos/modules/programs/kbdlight.nix b/nixos/modules/programs/kbdlight.nix index 30767a03291a..0172368e968f 100644 --- a/nixos/modules/programs/kbdlight.nix +++ b/nixos/modules/programs/kbdlight.nix @@ -11,13 +11,6 @@ in config = mkIf cfg.enable { environment.systemPackages = [ pkgs.kbdlight ]; - - security.permissionsWrappers.setuid = - [ { program = "kbdlight"; - source = "${pkgs.kbdlight.out}/bin/kbdlight"; - owner = "root"; - group = "root"; - setuid = true; - }]; + security.setuidPrograms = [ "kbdlight" ]; }; } diff --git a/nixos/modules/programs/light.nix b/nixos/modules/programs/light.nix index c89f8e937216..09cd1113d9c7 100644 --- a/nixos/modules/programs/light.nix +++ b/nixos/modules/programs/light.nix @@ -21,13 +21,6 @@ in config = mkIf cfg.enable { environment.systemPackages = [ pkgs.light ]; - - security.permissionsWrappers.setuid = - [ { program = "light"; - source = "${pkgs.light.out}/bin/light"; - owner = "root"; - group = "root"; - setuid = true; - }]; + security.setuidPrograms = [ "light" ]; }; } diff --git a/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix index 08d96cbcf4b5..c5a503180266 100644 --- a/nixos/modules/programs/shadow.nix +++ b/nixos/modules/programs/shadow.nix @@ -101,49 +101,9 @@ in chpasswd = { rootOK = true; }; }; - - security.permissionsWrappers.setuid = - [ - { program = "su"; - source = "${pkgs.shadow.su}/bin/su"; - owner = "root"; - group = "root"; - setuid = true; - } - - { program = "chfn"; - source = "${pkgs.shadow.out}/bin/chfn"; - owner = "root"; - group = "root"; - setuid = true; - } - ] ++ - (lib.optionals config.users.mutableUsers - map (x: x // { owner = "root"; - group = "root"; - setuid = true; - }) - [ - { program = "passwd"; - source = "${pkgs.shadow.out}/bin/passwd"; - } - - { program = "sg"; - source = "${pkgs.shadow.out}/bin/sg"; - } - - { program = "newgrp"; - source = "${pkgs.shadow.out}/bin/newgrp"; - } - - { program = "newuidmap"; - source = "${pkgs.shadow.out}/bin/newuidmap"; - } - - { program = "newgidmap"; - source = "${pkgs.shadow.out}/bin/newgidmap"; - } - ] - ); + security.setuidPrograms = [ + "su" "chfn" "newuidmap" "newgidmap" + ] ++ lib.optionals config.users.mutableUsers + [ "passwd" "sg" "newgrp" ]; }; } diff --git a/nixos/modules/security/apparmor-suid.nix b/nixos/modules/security/apparmor-suid.nix index 799f27b6708f..e7b870864ee2 100644 --- a/nixos/modules/security/apparmor-suid.nix +++ b/nixos/modules/security/apparmor-suid.nix @@ -19,7 +19,7 @@ with lib; config = mkIf (cfg.confineSUIDApplications) { security.apparmor.profiles = [ (pkgs.writeText "ping" '' #include - /var/permissions-wrappers/ping { + /run/wrappers/ping { #include #include #include @@ -33,7 +33,7 @@ with lib; ${pkgs.attr.out}/lib/libattr.so* mr, ${pkgs.iputils}/bin/ping mixr, - /var/permissions-wrappers/ping.real r, + /run/wrappers/ping.real r, #/etc/modules.conf r, diff --git a/nixos/modules/security/chromium-suid-sandbox.nix b/nixos/modules/security/chromium-suid-sandbox.nix index 6fe252136390..0699fbb728a7 100644 --- a/nixos/modules/security/chromium-suid-sandbox.nix +++ b/nixos/modules/security/chromium-suid-sandbox.nix @@ -27,10 +27,6 @@ in config = mkIf cfg.enable { environment.systemPackages = [ sandbox ]; - security.permissionsWrappers.setuid = [ - { program = sandbox.passthru.sandboxExecutableName; - source = "${sandbox}/bin/${sandbox.passthru.sandboxExecutableName}"; - } - ]; + security.setuidPrograms = [ sandbox.passthru.sandboxExecutableName ]; }; } diff --git a/nixos/modules/security/duosec.nix b/nixos/modules/security/duosec.nix index e5b354270153..ee62c34438e5 100644 --- a/nixos/modules/security/duosec.nix +++ b/nixos/modules/security/duosec.nix @@ -188,16 +188,7 @@ in environment.systemPackages = [ pkgs.duo-unix ]; - security.permissionsWrappers.setuid = - [ - { program = "login_duo"; - source = "${pkgs.duo-unix.out}/bin/login_duo"; - owner = "root"; - group = "root"; - setuid = true; - } - ]; - + security.setuidPrograms = [ "login_duo" ]; environment.etc = loginCfgFile ++ pamCfgFile; /* If PAM *and* SSH are enabled, then don't do anything special. diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix index c5088b64bb31..3c944acf6cfa 100644 --- a/nixos/modules/security/pam.nix +++ b/nixos/modules/security/pam.nix @@ -472,33 +472,14 @@ in ++ optionals config.security.pam.enableU2F [ pkgs.pam_u2f ] ++ optionals config.security.pam.enableEcryptfs [ pkgs.ecryptfs ]; - security.permissionsWrappers.setuid = - [ - { program = "unix_chkpwd"; - source = "${pkgs.pam}/sbin/unix_chkpwd.orig"; - owner = "root"; - group = "root"; - setuid = true; - } - - - - ] ++ (optional config.security.pam.enableEcryptfs - { program = "umount.ecryptfs_private"; - source = "${pkgs.ecryptfs.out}/bin/umount.ecryptfs_private"; - owner = "root"; - group = "root"; - setuid = true; - } - ) ++ (optional config.security.pam.enableEcryptfs - { program = "mount.ecryptfs_private"; - source = "${pkgs.ecryptfs.out}/bin/mount.ecryptfs_private"; - owner = "root"; - group = "root"; - setuid = true; - } - ); - + security.setuidPrograms = + optionals config.security.pam.enableEcryptfs [ "mount.ecryptfs_private" "umount.ecryptfs_private" ]; + + security.wrappers.unix_chkpwd = { + source = "${pkgs.pam}/sbin/unix_chkpwd.orig"; + owner = "root"; + setuid = true; + }; environment.etc = mapAttrsToList (n: v: makePAMService v) config.security.pam.services; diff --git a/nixos/modules/security/pam_usb.nix b/nixos/modules/security/pam_usb.nix index 53a7921a2440..032f8e38d111 100644 --- a/nixos/modules/security/pam_usb.nix +++ b/nixos/modules/security/pam_usb.nix @@ -33,22 +33,7 @@ in config = mkIf (cfg.enable || anyUsbAuth) { # Make sure pmount and pumount are setuid wrapped. - security.permissionsWrappers.setuid = - [ - { program = "pmount"; - source = "${pkgs.pmount.out}/bin/pmount"; - owner = "root"; - group = "root"; - setuid = true; - } - - { program = "pumount"; - source = "${pkgs.pmount.out}/bin/pumount"; - owner = "root"; - group = "root"; - setuid = true; - } - ]; + security.setuidPrograms = [ "pmount" "pumount" ]; environment.systemPackages = [ pkgs.pmount ]; diff --git a/nixos/modules/security/permissions-wrappers/default.nix b/nixos/modules/security/permissions-wrappers/default.nix deleted file mode 100644 index 480bd371040e..000000000000 --- a/nixos/modules/security/permissions-wrappers/default.nix +++ /dev/null @@ -1,191 +0,0 @@ -{ config, lib, pkgs, ... }: -let - - inherit (config.security) run-permissionsWrapperDir permissionsWrapperDir; - - isNotNull = v: if v != null then true else false; - - cfg = config.security.permissionsWrappers; - - setcapWrappers = import ./setcap-wrapper-drv.nix { - inherit config lib pkgs; - }; - - setuidWrappers = import ./setuid-wrapper-drv.nix { - inherit config lib pkgs; - }; - - ###### Activation script for the setcap wrappers - configureSetcapWrapper = - { program - , capabilities - , source ? null - , owner ? "nobody" - , group ? "nogroup" - }: '' - 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. - ${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" $permissionsWrapperDir/${program} - - # Set the executable bit - chmod u+rx,g+x,o+x $permissionsWrapperDir/${program} - ''; - - ###### Activation script for the setuid wrappers - configureSetuidWrapper = - { 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 = lib.mkOption { - type = lib.types.listOf lib.types.attrs; - default = []; - example = - [ { program = "ping"; - source = "${pkgs.iputils.out}/bin/ping"; - owner = "nobody"; - group = "nogroup"; - 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. - ''; - }; - - security.permissionsWrappers.setuid = lib.mkOption { - type = lib.types.listOf lib.types.attrs; - default = []; - example = - [ { program = "sendmail"; - source = "/nix/store/.../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 = lib.mkOption { - type = lib.types.path; - default = "/var/permissions-wrappers"; - internal = true; - description = '' - This option defines the path to the permissions wrappers. It - should not be overriden. - ''; - }; - - security.run-permissionsWrapperDir = lib.mkOption { - type = lib.types.path; - default = "/run/permissions-wrapper-dirs"; - internal = true; - description = '' - This option defines the run 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="${permissionsWrapperDir}:$PATH" - ''; - - system.activationScripts.wrapper-dir = '' - mkdir -p "${permissionsWrapperDir}" - ''; - - ###### setcap activation script - system.activationScripts.permissions-wrappers = - lib.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 - - mkdir -p ${run-permissionsWrapperDir} - permissionsWrapperDir=$(mktemp --directory --tmpdir=${run-permissionsWrapperDir} permissions-wrappers.XXXXXXXXXX) - chmod a+rx $permissionsWrapperDir - - ${lib.concatMapStrings configureSetcapWrapper (builtins.filter isNotNull cfg.setcap)} - ${lib.concatMapStrings configureSetuidWrapper (builtins.filter isNotNull cfg.setuid)} - - if [ -L ${permissionsWrapperDir} ]; then - # Atomically replace the symlink - # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/ - old=$(readlink ${permissionsWrapperDir}) - ln --symbolic --force --no-dereference $permissionsWrapperDir ${permissionsWrapperDir}-tmp - mv --no-target-directory ${permissionsWrapperDir}-tmp ${permissionsWrapperDir} - rm --force --recursive $old - elif [ -d ${permissionsWrapperDir} ]; then - # Compatibility with old state, just remove the folder and symlink - rm -f ${permissionsWrapperDir}/* - # if it happens to be a tmpfs - ${pkgs.utillinux}/bin/umount ${permissionsWrapperDir} || true - rm -d ${permissionsWrapperDir} - ln -d --symbolic $permissionsWrapperDir ${permissionsWrapperDir} - else - # For initial setup - ln --symbolic $permissionsWrapperDir ${permissionsWrapperDir} - fi - ''; - }; -} diff --git a/nixos/modules/security/permissions-wrappers/permissions-wrapper.c b/nixos/modules/security/permissions-wrappers/permissions-wrapper.c deleted file mode 100644 index cb9d8d6b37b2..000000000000 --- a/nixos/modules/security/permissions-wrappers/permissions-wrapper.c +++ /dev/null @@ -1,221 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#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 -// loudly if they are violated. -#undef NDEBUG - -extern char **environ; - -// The SOURCE_PROG and WRAPPER_DIR macros are supplied at compile time -// for a security reason: So they cannot be changed at runtime. -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 -#error "Program must be compiled with either the WRAPPER_SETCAP or WRAPPER_SETUID macro" -#endif - -// 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) -{ - capng_get_caps_process(); - - if (capng_update(CAPNG_ADD, CAPNG_INHERITABLE, (unsigned long) cap)) - { - printf("cannot raise the capability into the Inheritable set\n"); - exit(1); - } - - capng_apply(CAPNG_SELECT_CAPS); - - if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, (unsigned long) cap, 0, 0)) - { - perror("cannot raise the capability into the Ambient set\n"); - exit(1); - } -} - -// Given the path to this program, fetch its configured capability set -// (as set by `setcap ... /path/to/file`) and raise those capabilities -// into the Ambient set. -static int make_caps_ambient(const char *selfPath) -{ - cap_t caps = cap_get_file(selfPath); - - if(!caps) - { - fprintf(stderr, "could not retreive the capability set for this file\n"); - return 1; - } - - // We use `cap_to_text` and iteration over the tokenized result - // string because, as of libcap's current release, there is no - // facility for retrieving an array of `cap_value_t`'s that can be - // given to `prctl` in order to lift that capability into the - // Ambient set. - // - // Some discussion was had around shot-gunning all of the - // capabilities we know about into the Ambient set but that has a - // security smell and I deemed the risk of the current - // implementation crashing the program to be lower than the risk - // of a privilege escalation security hole being introduced by - // raising all capabilities, even ones we didn't intend for the - // program, into the Ambient set. - // - // `cap_t` which is returned by `cap_get_*` is an opaque type and - // even if we could retrieve the bitmasks (which, as far as I can - // tell we cannot) in order to get the `cap_value_t` - // representation for each capability we would have to take the - // total number of capabilities supported and iterate over the - // sequence of integers up-to that maximum total, testing each one - // against the bitmask ((bitmask >> n) & 1) to see if it's set and - // aggregating each "capability integer n" that is set in the - // bitmask. - // - // That, combined with the fact that we can't easily get the - // bitmask anyway seemed much more brittle than fetching the - // `cap_t`, transforming it into a textual representation, - // tokenizing the string, and using `cap_from_name` on the token - // to get the `cap_value_t` that we need for `prctl`. There is - // indeed risk involved if the output string format of - // `cap_to_text` ever changes but at this time the combination of - // factors involving the below list have led me to the conclusion - // that the best implementation at this time is reading then - // parsing with *lots of documentation* about why we're doing it - // this way. - // - // 1. No explicit API for fetching an array of `cap_value_t`'s or - // for transforming a `cap_t` into such a representation - // 2. The risk of a crash is lower than lifting all capabilities - // into the Ambient set - // 3. libcap is depended on heavily in the Linux ecosystem so - // there is a high chance that the output representation of - // `cap_to_text` will not change which reduces our risk that - // this parsing step will cause a crash - // - // The preferred method, should it ever be available in the - // future, would be to use libcap API's to transform the result - // from a `cap_get_*` into an array of `cap_value_t`'s that can - // then be given to prctl. - // - // - Parnell - ssize_t capLen; - char* capstr = cap_to_text(caps, &capLen); - cap_free(caps); - - // TODO: For now, we assume that cap_to_text always starts its - // result string with " =" and that the first capability is listed - // immediately after that. We should verify this. - assert(capLen >= 2); - capstr += 2; - - char* saveptr = NULL; - for(char* tok = strtok_r(capstr, ",", &saveptr); tok; tok = strtok_r(NULL, ",", &saveptr)) - { - cap_value_t capnum; - if (cap_from_name(tok, &capnum)) - { - fprintf(stderr, "cap_from_name failed, skipping: %s\n", tok); - } - else if (capnum == CAP_SETPCAP) - { - // Check for the cap_setpcap capability, we set this on the - // wrapper so it can elevate the capabilities to the Ambient - // set but we do not want to propagate it down into the - // wrapped program. - // - // TODO: what happens if that's the behavior you want - // though???? I'm preferring a strict vs. loose policy here. - fprintf(stderr, "cap_setpcap in set, skipping it\n"); - } - else - { - set_ambient_cap(capnum); - printf("raised %s into the Ambient capability set\n", tok); - } - } - cap_free(capstr); - - return 0; -} - -int main(int argc, char * * argv) -{ - // I *think* it's safe to assume that a path from a symbolic link - // should safely fit within the PATH_MAX system limit. Though I'm - // not positive it's safe... - char selfPath[PATH_MAX]; - int selfPathSize = readlink("/proc/self/exe", selfPath, sizeof(selfPath) - 1); - - assert(selfPathSize > 0); - - selfPath[selfPathSize] = '\0'; - - // Make sure that we are being executed from the right location, - // i.e., `safeWrapperDir'. 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 - // with elevated capabilities. - int len = strlen(wrapperDir); - if (len > 0 && '/' == wrapperDir[len - 1]) - --len; - assert(!strncmp(selfPath, wrapperDir, len)); - assert('/' == wrapperDir[0]); - assert('/' == selfPath[len]); - - // Make *really* *really* sure that we were executed as - // `selfPath', and not, say, as some other setuid program. That - // is, our effective uid/gid should match the uid/gid of - // `selfPath'. - struct stat st; - assert(lstat(selfPath, &st) != -1); - - assert(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid())); - assert(!(st.st_mode & S_ISGID) || (st.st_gid == getegid())); - - // And, of course, we shouldn't be writable. - assert(!(st.st_mode & (S_IWGRP | S_IWOTH))); - - struct stat stR; - stat(sourceProg, &stR); - - // Make sure the program we're wrapping is non-zero - assert(stR.st_size > 0); - - // Read the capabilities set on the file and raise them in to the - // Ambient set so the program we're wrapping receives the - // capabilities too! - if (strcmp(wrapperType, "setcap") == 0) - assert(!make_caps_ambient(selfPath)); - - execve(sourceProg, argv, environ); - - fprintf(stderr, "%s: cannot run `%s': %s\n", - argv[0], sourceProg, strerror(errno)); - - exit(1); -} - - diff --git a/nixos/modules/security/permissions-wrappers/setcap-wrapper-drv.nix b/nixos/modules/security/permissions-wrappers/setcap-wrapper-drv.nix deleted file mode 100644 index 2ae3067b1b1c..000000000000 --- a/nixos/modules/security/permissions-wrappers/setcap-wrapper-drv.nix +++ /dev/null @@ -1,37 +0,0 @@ -{ 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=\"${config.security.run-permissionsWrapperDir}\" \ - -lcap-ng -lcap ${./permissions-wrapper.c} -o $out/bin/${program}.wrapper -L ${pkgs.libcap.lib}/lib -L ${pkgs.libcap_ng}/lib \ - -I ${pkgs.libcap.dev}/include -I ${pkgs.libcap_ng}/include -I ${pkgs.linuxHeaders}/include - ''; -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 ]; - installPhase = '' - mkdir -p $out/bin - - # Concat together all of our shell splices to compile - # binary wrapper programs for all configured setcap programs. - ${lib.concatMapStrings mkSetcapWrapper cfg.setcap} - ''; -} diff --git a/nixos/modules/security/permissions-wrappers/setuid-wrapper-drv.nix b/nixos/modules/security/permissions-wrappers/setuid-wrapper-drv.nix deleted file mode 100644 index 53cce2ff48e7..000000000000 --- a/nixos/modules/security/permissions-wrappers/setuid-wrapper-drv.nix +++ /dev/null @@ -1,35 +0,0 @@ -{ 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=\"${config.security.run-permissionsWrapperDir}\" \ - -lcap-ng -lcap ${./permissions-wrapper.c} -o $out/bin/${program}.wrapper -L ${pkgs.libcap.lib}/lib -L ${pkgs.libcap_ng}/lib \ - -I ${pkgs.libcap.dev}/include -I ${pkgs.libcap_ng}/include -I ${pkgs.linuxHeaders}/include - ''; -in - -# This is only useful for Linux platforms and a kernel version of -# 4.3 or greater -assert pkgs.stdenv.isLinux; - -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. - ${lib.concatMapStrings mkSetuidWrapper cfg.setuid} - ''; -} diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix index 098319d5ded3..547b40cedfd9 100644 --- a/nixos/modules/security/polkit.nix +++ b/nixos/modules/security/polkit.nix @@ -83,22 +83,8 @@ in security.pam.services.polkit-1 = {}; - security.permissionsWrappers.setuid = - [ - { program = "pkexec"; - source = "${pkgs.polkit.out}/bin/pkexec"; - owner = "root"; - group = "root"; - setuid = true; - } - - { program = "polkit-agent-helper-1"; - owner = "root"; - group = "root"; - setuid = true; - source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1"; - } - ]; + security.setuidPrograms = [ "pkexec" ]; + security.wrappers."polkit-agent-helper-1".source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1"; system.activationScripts.polkit = '' diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix index 652f23c2938f..f5612e1b0c5d 100644 --- a/nixos/modules/security/sudo.nix +++ b/nixos/modules/security/sudo.nix @@ -81,22 +81,7 @@ in ${cfg.extraConfig} ''; - security.permissionsWrappers.setuid = - [ - { program = "sudo"; - source = "${pkgs.sudo.out}/bin/sudo"; - owner = "root"; - group = "root"; - setuid = true; - } - - { program = "sudoedit"; - source = "${pkgs.sudo.out}/bin/sudoedit"; - owner = "root"; - group = "root"; - setuid = true; - } - ]; + security.setuidPrograms = [ "sudo" "sudoedit" ]; environment.systemPackages = [ sudo ]; diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix new file mode 100644 index 000000000000..d12209b375b8 --- /dev/null +++ b/nixos/modules/security/wrappers/default.nix @@ -0,0 +1,191 @@ +{ config, lib, pkgs, ... }: +let + + inherit (config.security) wrapperDir; + + isNotNull = v: if v != null || v != "" then true else false; + + cfg = config.security.wrappers; + + setcapWrappers = import ./setcap-wrapper-drv.nix { + inherit config lib pkgs; + }; + + setuidWrappers = import ./setuid-wrapper-drv.nix { + inherit config lib pkgs; + }; + + ###### Activation script for the setcap wrappers + mkSetcapProgram = + { program + , capabilities + , source ? null + , owner ? "nobody" + , group ? "nogroup" + ... + }: '' + cp ${setcapWrappers}/bin/${program}.wrapper $wrapperDir/${program} + + # Prevent races + chmod 0000 $wrapperDir/${program} + chown ${owner}.${group} $wrapperDir/${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. + ${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" $wrapperDir/${program} + + # Set the executable bit + chmod u+rx,g+x,o+x $wrapperDir/${program} + ''; + + ###### Activation script for the setuid wrappers + mkSetuidProgram = + { program + , source ? null + , owner ? "nobody" + , group ? "nogroup" + , setuid ? false + , setgid ? false + , permissions ? "u+rx,g+x,o+x" + ... + }: '' + cp ${setuidWrappers}/bin/${program}.wrapper $wrapperDir/${program} + + # Prevent races + chmod 0000 $wrapperDir/${program} + chown ${owner}.${group} $wrapperDir/${program} + + chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" $wrapperDir/${program} + ''; +in +{ + + ###### interface + + options = { + security.wrappers.setcap = lib.mkOption { + type = lib.types.listOf lib.types.attrs; + default = []; + example = + [ { program = "ping"; + source = "${pkgs.iputils.out}/bin/ping"; + owner = "nobody"; + group = "nogroup"; + 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. + ''; + }; + + security.setuidPrograms = mkOption { + type = types.listOf types.str; + default = []; + example = ["passwd"]; + description = '' + The Nix store cannot contain setuid/setgid programs directly. + For this reason, NixOS can automatically generate wrapper + programs that have the necessary privileges. This option + lists the names of programs in the system environment for + which setuid root wrappers should be created. + ''; + }; + + security.wrappers = lib.mkOption { + type = lib.types.attrs; + default = {}; + example = { + sendmail.source = "/nix/store/.../bin/sendmail"; + }; + 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.old-wrapperDir = lib.mkOption { + type = lib.types.path; + default = "/var/setuid-wrappers"; + internal = true; + description = '' + This option defines the path to the wrapper programs. It + should not be overriden. + ''; + }; + + security.wrapperDir = lib.mkOption { + type = lib.types.path; + default = "/run/wrappers"; + internal = true; + description = '' + This option defines the path to the wrapper programs. 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="${wrapperDir}:$PATH" + ''; + + ###### setcap activation script + system.activationScripts.wrappers = + let + programs = + (map (x: { program = x; owner = "root"; group = "root"; setuid = true; }) + config.security.setuidPrograms) + ++ lib.mapAttrsToList + (n: v: (if v ? "program" then v else v // {program=n;})) + cfg.wrappers; + + wrapperPrograms = + builtins.map + (s: if (s ? "setuid" && s.setuid == true) || + (s ? "setguid" && s.setguid == true) || + (s ? "permissions") + then mkSetuidProgram s + else if (s ? "capabilities") + then mkSetcapProgram s + else "" + ) programs; + + in lib.stringAfter [ "users" ] + '' + # Look in the system path and in the default profile for + # programs to be wrapped. + WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin + + mkdir -p ${wrapperDir} + wrapperDir=$(mktemp --directory --tmpdir=${wrapperDir} wrappers.XXXXXXXXXX) + chmod a+rx $wrapperDir + + ${lib.concatStringsSep "\n" (builtins.filter isNotNull cfg.wrappers)} + ''; + }; +} diff --git a/nixos/modules/security/wrappers/permissions-wrapper.c b/nixos/modules/security/wrappers/permissions-wrapper.c new file mode 100644 index 000000000000..cb9d8d6b37b2 --- /dev/null +++ b/nixos/modules/security/wrappers/permissions-wrapper.c @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include +#include +#include +#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 +// loudly if they are violated. +#undef NDEBUG + +extern char **environ; + +// The SOURCE_PROG and WRAPPER_DIR macros are supplied at compile time +// for a security reason: So they cannot be changed at runtime. +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 +#error "Program must be compiled with either the WRAPPER_SETCAP or WRAPPER_SETUID macro" +#endif + +// 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) +{ + capng_get_caps_process(); + + if (capng_update(CAPNG_ADD, CAPNG_INHERITABLE, (unsigned long) cap)) + { + printf("cannot raise the capability into the Inheritable set\n"); + exit(1); + } + + capng_apply(CAPNG_SELECT_CAPS); + + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, (unsigned long) cap, 0, 0)) + { + perror("cannot raise the capability into the Ambient set\n"); + exit(1); + } +} + +// Given the path to this program, fetch its configured capability set +// (as set by `setcap ... /path/to/file`) and raise those capabilities +// into the Ambient set. +static int make_caps_ambient(const char *selfPath) +{ + cap_t caps = cap_get_file(selfPath); + + if(!caps) + { + fprintf(stderr, "could not retreive the capability set for this file\n"); + return 1; + } + + // We use `cap_to_text` and iteration over the tokenized result + // string because, as of libcap's current release, there is no + // facility for retrieving an array of `cap_value_t`'s that can be + // given to `prctl` in order to lift that capability into the + // Ambient set. + // + // Some discussion was had around shot-gunning all of the + // capabilities we know about into the Ambient set but that has a + // security smell and I deemed the risk of the current + // implementation crashing the program to be lower than the risk + // of a privilege escalation security hole being introduced by + // raising all capabilities, even ones we didn't intend for the + // program, into the Ambient set. + // + // `cap_t` which is returned by `cap_get_*` is an opaque type and + // even if we could retrieve the bitmasks (which, as far as I can + // tell we cannot) in order to get the `cap_value_t` + // representation for each capability we would have to take the + // total number of capabilities supported and iterate over the + // sequence of integers up-to that maximum total, testing each one + // against the bitmask ((bitmask >> n) & 1) to see if it's set and + // aggregating each "capability integer n" that is set in the + // bitmask. + // + // That, combined with the fact that we can't easily get the + // bitmask anyway seemed much more brittle than fetching the + // `cap_t`, transforming it into a textual representation, + // tokenizing the string, and using `cap_from_name` on the token + // to get the `cap_value_t` that we need for `prctl`. There is + // indeed risk involved if the output string format of + // `cap_to_text` ever changes but at this time the combination of + // factors involving the below list have led me to the conclusion + // that the best implementation at this time is reading then + // parsing with *lots of documentation* about why we're doing it + // this way. + // + // 1. No explicit API for fetching an array of `cap_value_t`'s or + // for transforming a `cap_t` into such a representation + // 2. The risk of a crash is lower than lifting all capabilities + // into the Ambient set + // 3. libcap is depended on heavily in the Linux ecosystem so + // there is a high chance that the output representation of + // `cap_to_text` will not change which reduces our risk that + // this parsing step will cause a crash + // + // The preferred method, should it ever be available in the + // future, would be to use libcap API's to transform the result + // from a `cap_get_*` into an array of `cap_value_t`'s that can + // then be given to prctl. + // + // - Parnell + ssize_t capLen; + char* capstr = cap_to_text(caps, &capLen); + cap_free(caps); + + // TODO: For now, we assume that cap_to_text always starts its + // result string with " =" and that the first capability is listed + // immediately after that. We should verify this. + assert(capLen >= 2); + capstr += 2; + + char* saveptr = NULL; + for(char* tok = strtok_r(capstr, ",", &saveptr); tok; tok = strtok_r(NULL, ",", &saveptr)) + { + cap_value_t capnum; + if (cap_from_name(tok, &capnum)) + { + fprintf(stderr, "cap_from_name failed, skipping: %s\n", tok); + } + else if (capnum == CAP_SETPCAP) + { + // Check for the cap_setpcap capability, we set this on the + // wrapper so it can elevate the capabilities to the Ambient + // set but we do not want to propagate it down into the + // wrapped program. + // + // TODO: what happens if that's the behavior you want + // though???? I'm preferring a strict vs. loose policy here. + fprintf(stderr, "cap_setpcap in set, skipping it\n"); + } + else + { + set_ambient_cap(capnum); + printf("raised %s into the Ambient capability set\n", tok); + } + } + cap_free(capstr); + + return 0; +} + +int main(int argc, char * * argv) +{ + // I *think* it's safe to assume that a path from a symbolic link + // should safely fit within the PATH_MAX system limit. Though I'm + // not positive it's safe... + char selfPath[PATH_MAX]; + int selfPathSize = readlink("/proc/self/exe", selfPath, sizeof(selfPath) - 1); + + assert(selfPathSize > 0); + + selfPath[selfPathSize] = '\0'; + + // Make sure that we are being executed from the right location, + // i.e., `safeWrapperDir'. 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 + // with elevated capabilities. + int len = strlen(wrapperDir); + if (len > 0 && '/' == wrapperDir[len - 1]) + --len; + assert(!strncmp(selfPath, wrapperDir, len)); + assert('/' == wrapperDir[0]); + assert('/' == selfPath[len]); + + // Make *really* *really* sure that we were executed as + // `selfPath', and not, say, as some other setuid program. That + // is, our effective uid/gid should match the uid/gid of + // `selfPath'. + struct stat st; + assert(lstat(selfPath, &st) != -1); + + assert(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid())); + assert(!(st.st_mode & S_ISGID) || (st.st_gid == getegid())); + + // And, of course, we shouldn't be writable. + assert(!(st.st_mode & (S_IWGRP | S_IWOTH))); + + struct stat stR; + stat(sourceProg, &stR); + + // Make sure the program we're wrapping is non-zero + assert(stR.st_size > 0); + + // Read the capabilities set on the file and raise them in to the + // Ambient set so the program we're wrapping receives the + // capabilities too! + if (strcmp(wrapperType, "setcap") == 0) + assert(!make_caps_ambient(selfPath)); + + execve(sourceProg, argv, environ); + + fprintf(stderr, "%s: cannot run `%s': %s\n", + argv[0], sourceProg, strerror(errno)); + + exit(1); +} + + diff --git a/nixos/modules/security/wrappers/setcap-wrapper-drv.nix b/nixos/modules/security/wrappers/setcap-wrapper-drv.nix new file mode 100644 index 000000000000..03dca5c9f42b --- /dev/null +++ b/nixos/modules/security/wrappers/setcap-wrapper-drv.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.security.wrappers; + + # 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=\"${config.security.run-wrapperDir}\" \ + -lcap-ng -lcap ${./permissions-wrapper.c} -o $out/bin/${program}.wrapper -L ${pkgs.libcap.lib}/lib -L ${pkgs.libcap_ng}/lib \ + -I ${pkgs.libcap.dev}/include -I ${pkgs.libcap_ng}/include -I ${pkgs.linuxHeaders}/include + ''; +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 ]; + installPhase = '' + mkdir -p $out/bin + + # Concat together all of our shell splices to compile + # binary wrapper programs for all configured setcap programs. + ${lib.concatMapStrings mkSetcapWrapper cfg.setcap} + ''; +} diff --git a/nixos/modules/security/wrappers/setuid-wrapper-drv.nix b/nixos/modules/security/wrappers/setuid-wrapper-drv.nix new file mode 100644 index 000000000000..e08ae799bf40 --- /dev/null +++ b/nixos/modules/security/wrappers/setuid-wrapper-drv.nix @@ -0,0 +1,35 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.security.wrappers; + + # 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=$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=\"${config.security.run-wrapperDir}\" \ + -lcap-ng -lcap ${./permissions-wrapper.c} -o $out/bin/${program}.wrapper -L ${pkgs.libcap.lib}/lib -L ${pkgs.libcap_ng}/lib \ + -I ${pkgs.libcap.dev}/include -I ${pkgs.libcap_ng}/include -I ${pkgs.linuxHeaders}/include + ''; +in + +# This is only useful for Linux platforms and a kernel version of +# 4.3 or greater +assert pkgs.stdenv.isLinux; + +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. + ${lib.concatMapStrings mkSetuidWrapper cfg.setuid} + ''; +} diff --git a/nixos/modules/services/logging/logcheck.nix b/nixos/modules/services/logging/logcheck.nix index 86451ec318c9..c933c4964798 100644 --- a/nixos/modules/services/logging/logcheck.nix +++ b/nixos/modules/services/logging/logcheck.nix @@ -29,8 +29,8 @@ let }; cronJob = '' - @reboot logcheck env PATH=/var/permissions-wrappers:$PATH nice -n10 ${pkgs.logcheck}/sbin/logcheck -R ${flags} - 2 ${cfg.timeOfDay} * * * logcheck env PATH=/var/permissions-wrappers:$PATH nice -n10 ${pkgs.logcheck}/sbin/logcheck ${flags} + @reboot logcheck env PATH=/run/wrappers:$PATH nice -n10 ${pkgs.logcheck}/sbin/logcheck -R ${flags} + 2 ${cfg.timeOfDay} * * * logcheck env PATH=/run/wrappers:$PATH nice -n10 ${pkgs.logcheck}/sbin/logcheck ${flags} ''; writeIgnoreRule = name: {level, regex, ...}: diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix index 6b37a8a4ea2c..7cea2f75439b 100644 --- a/nixos/modules/services/mail/dovecot.nix +++ b/nixos/modules/services/mail/dovecot.nix @@ -13,7 +13,7 @@ let '' base_dir = ${baseDir} protocols = ${concatStringsSep " " cfg.protocols} - sendmail_path = /var/permissions-wrappers/sendmail + sendmail_path = /run/wrappers/sendmail '' (if isNull cfg.sslServerCert then '' diff --git a/nixos/modules/services/mail/exim.nix b/nixos/modules/services/mail/exim.nix index 6dfb8fdef119..71414bddd5dc 100644 --- a/nixos/modules/services/mail/exim.nix +++ b/nixos/modules/services/mail/exim.nix @@ -70,7 +70,7 @@ in etc."exim.conf".text = '' exim_user = ${cfg.user} exim_group = ${cfg.group} - exim_path = /var/permissions-wrappers/exim + exim_path = /run/wrappers/exim spool_directory = ${cfg.spoolDir} ${cfg.config} ''; @@ -89,15 +89,7 @@ in gid = config.ids.gids.exim; }; - security.permissionsWrappers.setuid = - [ - { program = "exim"; - source = "${pkgs.exim.out}/bin/exim"; - owner = "root"; - group = "root"; - setuid = true; - } - ]; + security.setuidPrograms = [ "exim" ]; systemd.services.exim = { description = "Exim Mail Daemon"; diff --git a/nixos/modules/services/mail/mail.nix b/nixos/modules/services/mail/mail.nix index e8b16349f1a5..aef02eddbe1c 100644 --- a/nixos/modules/services/mail/mail.nix +++ b/nixos/modules/services/mail/mail.nix @@ -26,7 +26,7 @@ with lib; config = mkIf (config.services.mail.sendmailSetuidWrapper != null) { - security.permissionsWrappers.setuid = [ config.services.mail.sendmailSetuidWrapper ]; + security.wrappers.setuid = [ config.services.mail.sendmailSetuidWrapper ]; }; diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix index a80565fa280b..cd4a5125029f 100644 --- a/nixos/modules/services/monitoring/munin.nix +++ b/nixos/modules/services/monitoring/munin.nix @@ -34,7 +34,7 @@ let cap=$(sed -nr 's/.*#%#\s+capabilities\s*=\s*(.+)/\1/p' $file) wrapProgram $file \ - --set PATH "/var/permissions-wrappers:/run/current-system/sw/bin:/run/current-system/sw/bin" \ + --set PATH "/run/wrappers:/run/current-system/sw/bin:/run/current-system/sw/bin" \ --set MUNIN_LIBDIR "${pkgs.munin}/lib" \ --set MUNIN_PLUGSTATE "/var/run/munin" @@ -183,7 +183,7 @@ in mkdir -p /etc/munin/plugins rm -rf /etc/munin/plugins/* - PATH="/var/permissions-wrappers:/run/current-system/sw/bin:/run/current-system/sw/bin" ${pkgs.munin}/sbin/munin-node-configure --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${muninPlugins} --servicedir=/etc/munin/plugins 2>/dev/null | ${pkgs.bash}/bin/bash + PATH="/run/wrappers:/run/current-system/sw/bin:/run/current-system/sw/bin" ${pkgs.munin}/sbin/munin-node-configure --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${muninPlugins} --servicedir=/etc/munin/plugins 2>/dev/null | ${pkgs.bash}/bin/bash ''; serviceConfig = { ExecStart = "${pkgs.munin}/sbin/munin-node --config ${nodeConf} --servicedir /etc/munin/plugins/"; diff --git a/nixos/modules/services/monitoring/smartd.nix b/nixos/modules/services/monitoring/smartd.nix index 99fd5c4d3674..af02d73597fe 100644 --- a/nixos/modules/services/monitoring/smartd.nix +++ b/nixos/modules/services/monitoring/smartd.nix @@ -124,7 +124,7 @@ in }; mailer = mkOption { - default = "/var/permissions-wrappers/sendmail"; + default = "/run/wrappers/sendmail"; type = types.path; description = '' Sendmail-compatible binary to be used to send the messages. diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix index 884966363b8b..8cc8f21851c4 100644 --- a/nixos/modules/services/network-filesystems/samba.nix +++ b/nixos/modules/services/network-filesystems/samba.nix @@ -30,7 +30,7 @@ let '' [ global ] security = ${cfg.securityType} - passwd program = /var/permissions-wrappers/passwd %u + passwd program = /run/wrappers/passwd %u pam password change = ${smbToString cfg.syncPasswordsByPam} invalid users = ${smbToString cfg.invalidUsers} diff --git a/nixos/modules/services/networking/gale.nix b/nixos/modules/services/networking/gale.nix index bc9b884f11b4..f4c75c17290f 100644 --- a/nixos/modules/services/networking/gale.nix +++ b/nixos/modules/services/networking/gale.nix @@ -141,7 +141,7 @@ in setgid = false; }; - security.permissionsWrappers.setuid = [ cfg.setuidWrapper ]; + security.wrappers.setuid = [ cfg.setuidWrapper ]; systemd.services.gale-galed = { description = "Gale messaging daemon"; diff --git a/nixos/modules/services/networking/prayer.nix b/nixos/modules/services/networking/prayer.nix index 67d8cece6115..58e6ad8a683e 100644 --- a/nixos/modules/services/networking/prayer.nix +++ b/nixos/modules/services/networking/prayer.nix @@ -18,7 +18,7 @@ let var_prefix = "${stateDir}" prayer_user = "${prayerUser}" prayer_group = "${prayerGroup}" - sendmail_path = "/var/permissions-wrappers/sendmail" + sendmail_path = "/run/wrappers/sendmail" use_http_port ${cfg.port} diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix index 67aa313c8605..b7bb55f55084 100644 --- a/nixos/modules/services/networking/smokeping.nix +++ b/nixos/modules/services/networking/smokeping.nix @@ -219,14 +219,14 @@ in type = types.string; default = '' + FPing - binary = ${config.security.permissionsWrapperDir}/fping + binary = ${config.security.wrapperDir}/fping ''; description = "Probe configuration"; }; sendmail = mkOption { type = types.nullOr types.path; default = null; - example = "/var/permissions-wrappers/sendmail"; + example = "/run/wrappers/sendmail"; description = "Use this sendmail compatible script to deliver alerts"; }; smokeMailTemplate = mkOption { @@ -273,21 +273,7 @@ in message = "services.smokeping: sendmail and Mailhost cannot both be enabled."; } ]; - security.permissionsWrappers.setuid = [ - { program = "fping"; - source = "${pkgs.fping}/bin/fping"; - owner = "root"; - group = "root"; - setuid = true; - } - - { program = "fping"; - source = "${pkgs.fping}/bin/fping6"; - owner = "root"; - group = "root"; - setuid = true; - } - ]; + security.setuidPrograms = [ "fping" "fping6" ]; environment.systemPackages = [ pkgs.fping ]; users.extraUsers = singleton { name = cfg.user; diff --git a/nixos/modules/services/scheduling/atd.nix b/nixos/modules/services/scheduling/atd.nix index 9c4f8d59faa4..316ab847b343 100644 --- a/nixos/modules/services/scheduling/atd.nix +++ b/nixos/modules/services/scheduling/atd.nix @@ -42,7 +42,7 @@ in config = mkIf cfg.enable { - security.permissionsWrappers.setuid = map (program: { + security.wrappers.setuid = map (program: { inherit program; source = "${pkgs.atd}/bin/${program}"; diff --git a/nixos/modules/services/scheduling/cron.nix b/nixos/modules/services/scheduling/cron.nix index e33961658f02..26ce3c98d67c 100644 --- a/nixos/modules/services/scheduling/cron.nix +++ b/nixos/modules/services/scheduling/cron.nix @@ -20,7 +20,7 @@ let cronNixosPkg = pkgs.cron.override { # The mail.nix nixos module, if there is any local mail system enabled, # should have sendmail in this path. - sendmailPath = "/var/permissions-wrappers/sendmail"; + sendmailPath = "/run/wrappers/sendmail"; }; allFiles = @@ -61,7 +61,7 @@ in A list of Cron jobs to be appended to the system-wide crontab. See the manual page for crontab for the expected format. If you want to get the results mailed you must setuid - sendmail. See + sendmail. See If neither /var/cron/cron.deny nor /var/cron/cron.allow exist only root will is allowed to have its own crontab file. The /var/cron/cron.deny file @@ -92,21 +92,9 @@ in config = mkMerge [ { services.cron.enable = mkDefault (allFiles != []); } - (mkIf (config.services.cron.enable) { - - security.permissionsWrappers.setuid = - [ - { program = "crontab"; - source = "${pkgs.cronNixosPkg.out}/bin/crontab"; - owner = "root"; - group = "root"; - setuid = true; - } - ]; - + security.setuidPrograms = [ "crontab" ]; environment.systemPackages = [ cronNixosPkg ]; - environment.etc.crontab = { source = pkgs.runCommand "crontabs" { inherit allFiles; preferLocalBuild = true; } '' diff --git a/nixos/modules/services/scheduling/fcron.nix b/nixos/modules/services/scheduling/fcron.nix index 5804f0ee72f7..f0de996224ff 100644 --- a/nixos/modules/services/scheduling/fcron.nix +++ b/nixos/modules/services/scheduling/fcron.nix @@ -96,7 +96,7 @@ in fcronallow = /etc/fcron.allow fcrondeny = /etc/fcron.deny shell = /bin/sh - sendmail = /var/permissions-wrappers/sendmail + sendmail = /run/wrappers/sendmail editor = /run/current-system/sw/bin/vi ''; target = "fcron.conf"; @@ -106,16 +106,7 @@ in environment.systemPackages = [ pkgs.fcron ]; - security.permissionsWrappers.setuid = - [ - { program = "fcrontab"; - source = "${pkgs.fcron.out}/bin/fcrontab"; - owner = "root"; - group = "root"; - setuid = true; - } - ]; - + security.setuidPrograms = [ "fcrontab" ]; systemd.services.fcron = { description = "fcron daemon"; after = [ "local-fs.target" ]; diff --git a/nixos/modules/services/system/dbus.nix b/nixos/modules/services/system/dbus.nix index d15d5551e343..47fc4426af08 100644 --- a/nixos/modules/services/system/dbus.nix +++ b/nixos/modules/services/system/dbus.nix @@ -38,7 +38,7 @@ let sed -ri "s@/etc/dbus-1/(system|session)-@$out/\1-@" $out/{system,session}.conf sed '${./dbus-system-local.conf.in}' \ - -e 's,@servicehelper@,${config.security.permissionsWrapperDir}/dbus-daemon-launch-helper,g' \ + -e 's,@servicehelper@,${config.security.wrapperDir}/dbus-daemon-launch-helper,g' \ -e 's,@extra@,${systemExtraxml},' \ > "$out/system-local.conf" @@ -114,7 +114,7 @@ in systemd.packages = [ pkgs.dbus.daemon ]; - security.permissionsWrappers.setuid = singleton + security.wrappers.setuid = singleton { program = "dbus-daemon-launch-helper"; source = "${pkgs.dbus.daemon}/libexec/dbus-daemon-launch-helper"; owner = "root"; diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix index 9d0ff77c2ae8..feee6ba87ec8 100644 --- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix +++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix @@ -62,16 +62,7 @@ in ''; }]; - security.permissionsWrappers.setuid = - [ - { program = "e_freqset"; - source = "${e.enlightenment.out}/bin/e_freqset"; - owner = "root"; - group = "root"; - setuid = true; - } - ]; - + security.setuidPrograms = [ "e_freqset" ]; environment.etc = singleton { source = "${pkgs.xkeyboard_config}/etc/X11/xkb"; target = "X11/xkb"; diff --git a/nixos/modules/services/x11/desktop-managers/kde4.nix b/nixos/modules/services/x11/desktop-managers/kde4.nix index 31d2ebcdf1a6..d21a1f28dca3 100644 --- a/nixos/modules/services/x11/desktop-managers/kde4.nix +++ b/nixos/modules/services/x11/desktop-managers/kde4.nix @@ -131,7 +131,7 @@ in ''; }; - security.permissionsWrappers.setuid = singleton + security.wrappers.setuid = singleton { program = "kcheckpass"; source = "${kde_workspace}/lib/kde4/libexec/kcheckpass"; owner = "root"; diff --git a/nixos/modules/services/x11/desktop-managers/kde5.nix b/nixos/modules/services/x11/desktop-managers/kde5.nix index f886c60793d9..a4124aaefa9a 100644 --- a/nixos/modules/services/x11/desktop-managers/kde5.nix +++ b/nixos/modules/services/x11/desktop-managers/kde5.nix @@ -68,7 +68,7 @@ in ''; }; - security.permissionsWrappers.setuid = [ + security.wrappers.setuid = [ { program = "kcheckpass"; source = "${kde5.plasma-workspace.out}/lib/libexec/kcheckpass"; diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh index 86f552cd3caf..ffc0700806c6 100644 --- a/nixos/modules/system/boot/stage-2-init.sh +++ b/nixos/modules/system/boot/stage-2-init.sh @@ -131,10 +131,10 @@ if [ -n "@useHostResolvConf@" -a -e /etc/resolv.conf ]; then cat /etc/resolv.conf | resolvconf -m 1000 -a host fi -# Create /var/permissions-wrappers as a tmpfs. -rm -rf /var/permissions-wrappers -mkdir -m 0755 -p /var/permissions-wrappers -mount -t tmpfs -o "mode=0755" tmpfs /var/permissions-wrappers +# Create /run/wrappers as a tmpfs. +rm -rf /run/wrappers +mkdir -m 0755 -p /run/wrappers +mount -t tmpfs -o "mode=0755" tmpfs /run/wrappers # Log the script output to /dev/kmsg or /run/log/stage-2-init.log. # Only at this point are all the necessary prerequisites ready for these commands. diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 61519c6a3ce8..1afcddd915f7 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -898,38 +898,23 @@ in # Capabilities won't work unless we have at-least a 4.3 Linux # kernel because we need the ambient capability - security.permissionsWrappers.setcap = mkIf (versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.3") ( - [ - { program = "ping"; - source = "${pkgs.iputils.out}/bin/ping"; - capabilities = "cap_net_raw+p"; - } + security.wrappers = mkIf (versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.3") { + ping = { + source = "${pkgs.iputils.out}/bin/ping"; + capabilities = "cap_net_raw+p"; + }; - { program = "ping6"; - source = "${pkgs.iputils.out}/bin/ping6"; - capabilities = "cap_net_raw+p"; - } - ] - ); - - # If our linux kernel IS older than 4.3, let's setuid ping and ping6 - security.permissionsWrappers.setuid = mkIf (versionOlder (getVersion config.boot.kernelPackages.kernel) "4.3") ( - [ - { program = "ping"; - source = "${pkgs.iputils.out}/bin/ping"; - owner = "root"; - group = "root"; - setuid = true; - } - - { program = "ping6"; - source = "${pkgs.iputils.out}/bin/ping6"; - owner = "root"; - group = "root"; - setuid = true; - } - ] - ); + ping6 = { + source = "${pkgs.iputils.out}/bin/ping6"; + capabilities = "cap_net_raw+p"; + }; + }; + + # If the linux kernel IS older than 4.3, create setuid wrappers + # for ping and ping6 + security.setuidPrograms = mkIf (versionOlder (getVersion config.boot.kernelPackages.kernel) "4.3") [ + "ping" "ping6" + ]; # Set the host and domain names in the activation script. Don't # clear it if it's not configured in the NixOS configuration, diff --git a/nixos/modules/virtualisation/virtualbox-host.nix b/nixos/modules/virtualisation/virtualbox-host.nix index b3647482f2cb..405a630dfa78 100644 --- a/nixos/modules/virtualisation/virtualbox-host.nix +++ b/nixos/modules/virtualisation/virtualbox-host.nix @@ -68,7 +68,7 @@ in boot.extraModulePackages = [ kernelModules ]; environment.systemPackages = [ virtualbox ]; - security.permissionsWrappers.setuid = let + security.wrappers.setuid = let mkSuid = program: { inherit program; source = "${virtualbox}/libexec/virtualbox/${program}"; @@ -99,7 +99,7 @@ in SUBSYSTEM=="usb", ACTION=="remove", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor" ''; - # Since we lack the right setuid binaries, set up a host-only network by default. + # Since we lack the right setuid/setcap binaries, set up a host-only network by default. } (mkIf cfg.addNetworkInterface { systemd.services."vboxnet0" = { description = "VirtualBox vboxnet0 Interface"; diff --git a/nixos/tests/smokeping.nix b/nixos/tests/smokeping.nix index 7e2d84f44224..5e2d013abc52 100644 --- a/nixos/tests/smokeping.nix +++ b/nixos/tests/smokeping.nix @@ -14,7 +14,7 @@ import ./make-test.nix ({ pkgs, ...} : { mailHost = "127.0.0.2"; probeConfig = '' + FPing - binary = /var/permissions-wrappers/fping + binary = /run/wrappers/fping offset = 0% ''; }; -- cgit 1.4.1