Loading nixos/modules/security/wrappers/default.nix +10 −10 Original line number Diff line number Diff line Loading @@ -5,8 +5,8 @@ let parentWrapperDir = dirOf wrapperDir; securityWrapper = pkgs.callPackage ./wrapper.nix { inherit parentWrapperDir; securityWrapper = sourceProg : pkgs.callPackage ./wrapper.nix { inherit sourceProg; }; fileModeType = Loading Loading @@ -91,8 +91,7 @@ let , ... }: '' cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}" echo -n "${source}" > "$wrapperDir/${program}.real" cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}" # Prevent races chmod 0000 "$wrapperDir/${program}" Loading @@ -119,8 +118,7 @@ let , ... }: '' cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}" echo -n "${source}" > "$wrapperDir/${program}.real" cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}" # Prevent races chmod 0000 "$wrapperDir/${program}" Loading Loading @@ -248,11 +246,13 @@ in export PATH="${wrapperDir}:$PATH" ''; security.apparmor.includes."nixos/security.wrappers" = '' include "${pkgs.apparmorRulesFromClosure { name="security.wrappers"; } [ securityWrapper security.apparmor.includes = lib.mapAttrs' (wrapName: wrap: lib.nameValuePair "nixos/security.wrappers/${wrapName}" '' include "${pkgs.apparmorRulesFromClosure { name="security.wrappers.${wrapName}"; } [ (securityWrapper wrap.source) ]}" ''; mrpx ${wrap.source}, '') wrappers; ###### wrappers activation script system.activationScripts.wrappers = Loading nixos/modules/security/wrappers/wrapper.c +7 −102 Original line number Diff line number Diff line Loading @@ -17,6 +17,10 @@ #include <syscall.h> #include <byteswap.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)) // aborts when returns non-zero, printing the failed expression and errno Loading @@ -24,10 +28,6 @@ extern char **environ; // The WRAPPER_DIR macro is supplied at compile time so that it cannot // be changed at runtime static char *wrapper_dir = WRAPPER_DIR; // Wrapper debug variable name static char *wrapper_debug = "WRAPPER_DEBUG"; Loading Loading @@ -151,115 +151,20 @@ static int make_caps_ambient(const char *self_path) { return 0; } int readlink_malloc(const char *p, char **ret) { size_t l = FILENAME_MAX+1; int r; for (;;) { char *c = calloc(l, sizeof(char)); if (!c) { return -ENOMEM; } ssize_t n = readlink(p, c, l-1); if (n < 0) { r = -errno; free(c); return r; } if ((size_t) n < l-1) { c[n] = 0; *ret = c; return 0; } free(c); l *= 2; } } int main(int argc, char **argv) { ASSERT(argc >= 1); char *self_path = NULL; int self_path_size = readlink_malloc("/proc/self/exe", &self_path); if (self_path_size < 0) { fprintf(stderr, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size)); } unsigned int ruid, euid, suid, rgid, egid, sgid; MUSTSUCCEED(getresuid(&ruid, &euid, &suid)); MUSTSUCCEED(getresgid(&rgid, &egid, &sgid)); // If true, then we did not benefit from setuid privilege escalation, // where the original uid is still in ruid and different from euid == suid. int didnt_suid = (ruid == euid) && (euid == suid); // If true, then we did not benefit from setgid privilege escalation int didnt_sgid = (rgid == egid) && (egid == sgid); // Make sure that we are being executed from the right location, // i.e., `safe_wrapper_dir'. 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(wrapper_dir); if (len > 0 && '/' == wrapper_dir[len - 1]) --len; ASSERT(!strncmp(self_path, wrapper_dir, len)); ASSERT('/' == wrapper_dir[0]); ASSERT('/' == self_path[len]); // If we got privileges with the fs set[ug]id bit, check that the privilege we // got matches the one one we expected, ie that our effective uid/gid // matches the uid/gid of `self_path`. This ensures that we were executed as // `self_path', and not, say, as some other setuid program. // We don't check that if we did not benefit from the set[ug]id bit, as // can be the case in nosuid mounts or user namespaces. struct stat st; ASSERT(lstat(self_path, &st) != -1); // if the wrapper gained privilege with suid, check that we got the uid of the file owner ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == euid)); // if the wrapper gained privilege with sgid, check that we got the gid of the file group ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == egid)); // same, but with suid instead of euid ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == suid)); ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == sgid)); // 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 <self>.real. char real_fn[PATH_MAX + 10]; int real_fn_size = snprintf(real_fn, sizeof(real_fn), "%s.real", self_path); ASSERT(real_fn_size < sizeof(real_fn)); int fd_self = open(real_fn, O_RDONLY); ASSERT(fd_self != -1); char source_prog[PATH_MAX]; len = read(fd_self, source_prog, PATH_MAX); ASSERT(len != -1); ASSERT(len < sizeof(source_prog)); ASSERT(len > 0); source_prog[len] = 0; close(fd_self); // 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! if (make_caps_ambient(self_path) != 0) { free(self_path); if (make_caps_ambient("/proc/self/exe") != 0) { return 1; } free(self_path); execve(source_prog, argv, environ); execve(SOURCE_PROG, argv, environ); fprintf(stderr, "%s: cannot run `%s': %s\n", argv[0], source_prog, strerror(errno)); argv[0], SOURCE_PROG, strerror(errno)); return 1; } nixos/modules/security/wrappers/wrapper.nix +2 −2 Original line number Diff line number Diff line { stdenv, linuxHeaders, parentWrapperDir, debug ? false }: { stdenv, linuxHeaders, sourceProg, debug ? false }: # For testing: # $ nix-build -E 'with import <nixpkgs> {}; pkgs.callPackage ./wrapper.nix { parentWrapperDir = "/run/wrappers"; debug = true; }' stdenv.mkDerivation { Loading @@ -7,7 +7,7 @@ stdenv.mkDerivation { dontUnpack = true; hardeningEnable = [ "pie" ]; CFLAGS = [ ''-DWRAPPER_DIR="${parentWrapperDir}"'' ''-DSOURCE_PROG="${sourceProg}"'' ] ++ (if debug then [ "-Werror" "-Og" "-g" ] else [ Loading nixos/modules/system/boot/networkd.nix +2 −3 Original line number Diff line number Diff line Loading @@ -2814,9 +2814,8 @@ let systemd.services.systemd-networkd = let isReloadableUnitFileName = unitFileName: strings.hasSuffix ".network" unitFileName; partitionedUnitFiles = lib.partition isReloadableUnitFileName unitFiles; reloadableUnitFiles = partitionedUnitFiles.right; nonReloadableUnitFiles = partitionedUnitFiles.wrong; reloadableUnitFiles = attrsets.filterAttrs (k: v: isReloadableUnitFileName k) unitFiles; nonReloadableUnitFiles = attrsets.filterAttrs (k: v: !isReloadableUnitFileName k) unitFiles; unitFileSources = unitFiles: map (x: x.source) (attrValues unitFiles); in { wantedBy = [ "multi-user.target" ]; Loading nixos/modules/tasks/network-interfaces.nix +2 −4 Original line number Diff line number Diff line Loading @@ -1396,14 +1396,12 @@ in security.apparmor.policies."bin.ping".profile = lib.mkIf config.security.apparmor.policies."bin.ping".enable (lib.mkAfter '' /run/wrappers/bin/ping { include <abstractions/base> include <nixos/security.wrappers> include <nixos/security.wrappers/ping> rpx /run/wrappers/wrappers.*/ping, } /run/wrappers/wrappers.*/ping { include <abstractions/base> include <nixos/security.wrappers> r /run/wrappers/wrappers.*/ping.real, mrpx ${config.security.wrappers.ping.source}, include <nixos/security.wrappers/ping> capability net_raw, capability setpcap, } Loading Loading
nixos/modules/security/wrappers/default.nix +10 −10 Original line number Diff line number Diff line Loading @@ -5,8 +5,8 @@ let parentWrapperDir = dirOf wrapperDir; securityWrapper = pkgs.callPackage ./wrapper.nix { inherit parentWrapperDir; securityWrapper = sourceProg : pkgs.callPackage ./wrapper.nix { inherit sourceProg; }; fileModeType = Loading Loading @@ -91,8 +91,7 @@ let , ... }: '' cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}" echo -n "${source}" > "$wrapperDir/${program}.real" cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}" # Prevent races chmod 0000 "$wrapperDir/${program}" Loading @@ -119,8 +118,7 @@ let , ... }: '' cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}" echo -n "${source}" > "$wrapperDir/${program}.real" cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}" # Prevent races chmod 0000 "$wrapperDir/${program}" Loading Loading @@ -248,11 +246,13 @@ in export PATH="${wrapperDir}:$PATH" ''; security.apparmor.includes."nixos/security.wrappers" = '' include "${pkgs.apparmorRulesFromClosure { name="security.wrappers"; } [ securityWrapper security.apparmor.includes = lib.mapAttrs' (wrapName: wrap: lib.nameValuePair "nixos/security.wrappers/${wrapName}" '' include "${pkgs.apparmorRulesFromClosure { name="security.wrappers.${wrapName}"; } [ (securityWrapper wrap.source) ]}" ''; mrpx ${wrap.source}, '') wrappers; ###### wrappers activation script system.activationScripts.wrappers = Loading
nixos/modules/security/wrappers/wrapper.c +7 −102 Original line number Diff line number Diff line Loading @@ -17,6 +17,10 @@ #include <syscall.h> #include <byteswap.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)) // aborts when returns non-zero, printing the failed expression and errno Loading @@ -24,10 +28,6 @@ extern char **environ; // The WRAPPER_DIR macro is supplied at compile time so that it cannot // be changed at runtime static char *wrapper_dir = WRAPPER_DIR; // Wrapper debug variable name static char *wrapper_debug = "WRAPPER_DEBUG"; Loading Loading @@ -151,115 +151,20 @@ static int make_caps_ambient(const char *self_path) { return 0; } int readlink_malloc(const char *p, char **ret) { size_t l = FILENAME_MAX+1; int r; for (;;) { char *c = calloc(l, sizeof(char)); if (!c) { return -ENOMEM; } ssize_t n = readlink(p, c, l-1); if (n < 0) { r = -errno; free(c); return r; } if ((size_t) n < l-1) { c[n] = 0; *ret = c; return 0; } free(c); l *= 2; } } int main(int argc, char **argv) { ASSERT(argc >= 1); char *self_path = NULL; int self_path_size = readlink_malloc("/proc/self/exe", &self_path); if (self_path_size < 0) { fprintf(stderr, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size)); } unsigned int ruid, euid, suid, rgid, egid, sgid; MUSTSUCCEED(getresuid(&ruid, &euid, &suid)); MUSTSUCCEED(getresgid(&rgid, &egid, &sgid)); // If true, then we did not benefit from setuid privilege escalation, // where the original uid is still in ruid and different from euid == suid. int didnt_suid = (ruid == euid) && (euid == suid); // If true, then we did not benefit from setgid privilege escalation int didnt_sgid = (rgid == egid) && (egid == sgid); // Make sure that we are being executed from the right location, // i.e., `safe_wrapper_dir'. 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(wrapper_dir); if (len > 0 && '/' == wrapper_dir[len - 1]) --len; ASSERT(!strncmp(self_path, wrapper_dir, len)); ASSERT('/' == wrapper_dir[0]); ASSERT('/' == self_path[len]); // If we got privileges with the fs set[ug]id bit, check that the privilege we // got matches the one one we expected, ie that our effective uid/gid // matches the uid/gid of `self_path`. This ensures that we were executed as // `self_path', and not, say, as some other setuid program. // We don't check that if we did not benefit from the set[ug]id bit, as // can be the case in nosuid mounts or user namespaces. struct stat st; ASSERT(lstat(self_path, &st) != -1); // if the wrapper gained privilege with suid, check that we got the uid of the file owner ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == euid)); // if the wrapper gained privilege with sgid, check that we got the gid of the file group ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == egid)); // same, but with suid instead of euid ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == suid)); ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == sgid)); // 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 <self>.real. char real_fn[PATH_MAX + 10]; int real_fn_size = snprintf(real_fn, sizeof(real_fn), "%s.real", self_path); ASSERT(real_fn_size < sizeof(real_fn)); int fd_self = open(real_fn, O_RDONLY); ASSERT(fd_self != -1); char source_prog[PATH_MAX]; len = read(fd_self, source_prog, PATH_MAX); ASSERT(len != -1); ASSERT(len < sizeof(source_prog)); ASSERT(len > 0); source_prog[len] = 0; close(fd_self); // 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! if (make_caps_ambient(self_path) != 0) { free(self_path); if (make_caps_ambient("/proc/self/exe") != 0) { return 1; } free(self_path); execve(source_prog, argv, environ); execve(SOURCE_PROG, argv, environ); fprintf(stderr, "%s: cannot run `%s': %s\n", argv[0], source_prog, strerror(errno)); argv[0], SOURCE_PROG, strerror(errno)); return 1; }
nixos/modules/security/wrappers/wrapper.nix +2 −2 Original line number Diff line number Diff line { stdenv, linuxHeaders, parentWrapperDir, debug ? false }: { stdenv, linuxHeaders, sourceProg, debug ? false }: # For testing: # $ nix-build -E 'with import <nixpkgs> {}; pkgs.callPackage ./wrapper.nix { parentWrapperDir = "/run/wrappers"; debug = true; }' stdenv.mkDerivation { Loading @@ -7,7 +7,7 @@ stdenv.mkDerivation { dontUnpack = true; hardeningEnable = [ "pie" ]; CFLAGS = [ ''-DWRAPPER_DIR="${parentWrapperDir}"'' ''-DSOURCE_PROG="${sourceProg}"'' ] ++ (if debug then [ "-Werror" "-Og" "-g" ] else [ Loading
nixos/modules/system/boot/networkd.nix +2 −3 Original line number Diff line number Diff line Loading @@ -2814,9 +2814,8 @@ let systemd.services.systemd-networkd = let isReloadableUnitFileName = unitFileName: strings.hasSuffix ".network" unitFileName; partitionedUnitFiles = lib.partition isReloadableUnitFileName unitFiles; reloadableUnitFiles = partitionedUnitFiles.right; nonReloadableUnitFiles = partitionedUnitFiles.wrong; reloadableUnitFiles = attrsets.filterAttrs (k: v: isReloadableUnitFileName k) unitFiles; nonReloadableUnitFiles = attrsets.filterAttrs (k: v: !isReloadableUnitFileName k) unitFiles; unitFileSources = unitFiles: map (x: x.source) (attrValues unitFiles); in { wantedBy = [ "multi-user.target" ]; Loading
nixos/modules/tasks/network-interfaces.nix +2 −4 Original line number Diff line number Diff line Loading @@ -1396,14 +1396,12 @@ in security.apparmor.policies."bin.ping".profile = lib.mkIf config.security.apparmor.policies."bin.ping".enable (lib.mkAfter '' /run/wrappers/bin/ping { include <abstractions/base> include <nixos/security.wrappers> include <nixos/security.wrappers/ping> rpx /run/wrappers/wrappers.*/ping, } /run/wrappers/wrappers.*/ping { include <abstractions/base> include <nixos/security.wrappers> r /run/wrappers/wrappers.*/ping.real, mrpx ${config.security.wrappers.ping.source}, include <nixos/security.wrappers/ping> capability net_raw, capability setpcap, } Loading