Change:
Hello, TLDR: Currently there is no way to set for a program. After reading , I decided that it will be nice to add nixos module for wireshark. Although for many programs and wireshark in particular, its recommended to use granular linux capabilities in favor to more dangerous setuid technique(and I think that we all can agree that additional proxies like setuidWrapper only add more problems security-wise). It turns out that its impossible to do something like setuidWrapper for capabilities, because of capability inheritance though execv call(eg. you will also need to set capabilities on executed file). So as I see it, capabilities should be set directly in nixstore(which is something that is not currently possible i guess?) and that means that nix daemon should have be able to set capabilities and users should not be able to install packages(at least not special ones). And I guess that should be applied to setuid binaries too, because that situation is not fundamentally different. And that brings up even more problems, eg who should manage groups now(eg i want dumpcap to be executed only by group)? That being said, I dislike both approaches and i am looking forward to see what can be done about it. Thanks
For services there is serviceConfig.CapabilityBoundingSet and AppArmor has capability rules. If you only care about ad-hoc control of capabilities for specific executables, firejail has a capability filter feature, which I'm sure you could quite easily replicate in a custom setuid C wrapper. In the end, though, I think a rule-based MAC is the only solution which can address all your points (e.g., user/group capabilities). I'm afraid I don't quite understand your points about writing stuff to the nix store, but I'm sure others will be able to comment on that.
What's the problem with setuid wrappers? Binaries get copied to the wrapper directory from the nix store, so you can set the needed bits.
well, CapabilityBoundingSet systemd option is nice, but it is only limited to services and it's purpose is a little bit different, it's used to limit process capabilities while running it as root, but I need to gain capabilities, while running it as user. I was not aware of firejail and it seems nice tool but it looks like overkill for such simple thing as setting capabilities for a binary. And firejail needs setuid itself. Let me show it by example: ping binary can be run either with suid or with CAPNETRAW set. In former way, it will be runned unprivileged and won't be able to damage host system. Setting capability is what most linux distros do and it is very trivial - you just run setcap CAPNETRAW /bin/ping and it adds capability info to the file. It is easy, clean and out-of-the-box solution opposing to something like firejail which probably has much larger codebase and complexity and introduces new point of failure. Regarding nixstore - I meant that from some point of view, it would be nice to have suid/capabilities in nix store, because then someone who is not using NixOS or is not using NixOS module could benefit from it.(eg run wireshark flawlessly). But that in turn introduces a whole new set of problems. Well, there is no problem, but it doesn't work with capabilities and introduces a new point of failure security-wise. Don't get me wrong, there are a lot of check there, but with all respect I can't say if that is enough. Anyway, my initial point was that it's impossible to have capabilities on file. As showed it is kinda possible with tools like firejail, but they do it other way around - they drop root privileges and are IMO overkill. But that also means that probably it is possible to make something like current setuid wrapper, but that drops privileges before execv. But I personally think that it is worse than simple and clean traditional way.
FWIW I agree that setcap would be cool and clearly superior to setuid where applicable.
I don't get why it doesn't work if you copy the file and set the caps.
Do you mean without setuidWrapper thing? Because setuidWrapper doesn't copy binary, it copies special program that execv's it. Well, copying binary should work i guess... Now I wonder why setuidWrapper doesn't do that? And it seems less agile in terms of having multiple versions of same package and other neat nix features.
You can still create a wrapper with execve and set the caps to be inherited, no? I just fear problems security-wise of having setuid or caps in the nix store.
Oh I guess the file in the nix store must have inheritable caps set :( So execve wrapper with effective+inheritable caps, and the nix store program with inheritable caps.... as far as I understand.
Yes, that's correct On Mar 12, 2015 8:12 PM, "lethalman" wrote:
There definetly will be security problems if we just let everyone set caps in nix store, I think you can effectively have suid analog with capabilities. On Mar 12, 2015 8:00 PM, "lethalman" wrote:
and for that matter, why we can't just copy binaries without wrapper? I wonder if those symlinks to nixstore can be changed without running nixos-rebuild? Because we can always recopy right ones in activation scripts.
that's what I suggested, copying the binary itself. The problem will be with those programs requiring the binary to be in the same directory of other binaries ecc., i.e. in their original directory. So the setuid wrapper doing execve is more general. But I think it's ok for a good number of executables to be copied elsewhere the nix store. If it works, just go ahead for wireshark.
I wonder if there are such programs. Well ok, copying will work in some cases(I just tested with dumpcap, it works) but it looks even worse and hacky to me, because now we copy potentially large binaries out of nixstore where they belong and loose all nix features. And it doesn't work without nixos.
Yes there are such programs. And yes it's certainly ugly, perhaps has a better solution. , Nikita Mikhailov wrote:
Inheritable capabilities seem to be acceptable for sometimes having in store, compared to other options... Maybe make all this handling a config option.
that's true, but you still have to create the execve wrapper with effective caps. Not sure if it's worth the complication if you can just copy the whole binary.
After my experience with TeXLive binaries I don't trust copying binaries...
Won't it cause problems with insufficient privileges in nix-daemon, copy-closure stuff and binary caches?
We already have optional things requiring root access, like chroot builds.
As a side note, if chroots require root I think this can be changed to use CLONE_NEWUSER and not require root anymore. , Michael Raskin wrote:
What about getting write access to store (our bind mounts prevent even root processes from store writes, special bind remounts are used to give builders write access). I guess we also need UID NS for build user isolation.
Ok, so to summarize, I see these options: something like suid wrapper which will be suid'ed and will drop privileges and then execv required binary. pro Relatively easy to add. con Ugly - works only on NixOS, doesn't support multiple versions and other nix features. Adds wrappers and unneeded potentially exploitable code for what is used to be very simple. binaries to suid path and set capabilities there. pro Very easy to implement. con Ugly - works only on NixOS, doesn't support multiple versions and other nix features. Probably won't for in some cases where apps have additional directory structure requirements. support for setting Inheritable capabilities in nix store, NAR, etc. This should not affect security of nix-store. This will still need a wrapper, but it will be con Ugly - works only on NixOS, doesn't support multiple versions and other nix features. Relativly difficult, requires some work in Nix and NixOS support for setting extended attributes/setuid in nix store, NAR, etc. This will also require an access model, which will restrict unprivileged users from importing privileged packages in nixstore. This proposal is somewhat relative to in terms of extending nixstore capabilities. pro cleanest solution - no ugly hacks like /var/setuid-wrappers, etc. privileged apps will benefit from nix features(multiple versions, etc.) privileged apps won't require nixos modules or/and nixos at all. this will make possible to use PaX/Grsecurity features and improve security as requested in con hard to implement will require significant nixstore complication requres blessing. And personally, I don't think such large thing could be merged with current type of management(no offense). requires non-trivial policy management inside Nix (thanks Personally, I prefer 4th solution as it is clean and nix-friendly. Also I think it was bad decision to leave this kind of stuff out of whitepaper.
Also it requires non-trivial policy management inside Nix. Also, it means that Nix has to behave very differently on Linux and on FreeBSD.
I agree about policy management. About different underlying systems: I thought that it will be left to package maintainer, eg. on systems where capabilities are not available suid will be set instead.
What is the current status? How could I help?
Recently, ambient capabilities were merged: This makes it easy to implement a setcap wrapper; we have after the wrapper sets PRCAPAMBIENT. So the activation script can just set the capabilities directly on the wrapper. Unfortunately, Linux 4.3 is a few months away.
We have now kernel 4.4 ;)
A wrapper is something we need, has there been any movement by anyone on this? How can I help or get started if someone else hasn't already?
I've built the setcap-wrapper functionality and system, is there interest a pull request for this?
Here is my branch, I'm preparing a Pull Request but won't submit until I get a successful build on Ubuntu as-per the PR template.

Discussion:
# 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/setuid-wrappers"; <ins> next if $mountPoint eq "/var/setcap-wrappers"; </ins> # Skip the optional fields. my $n = 6; $n++ while $fields[$n] ne "-"; $n++;

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 <ins> mkdir -m 0755 -p $mountPoint/var/permissions-wrappers </ins> 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 <ins> mount -t tmpfs -o "mode=0755" none $mountPoint/var/permissions-wrappers </ins> 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

./security/rngd.nix ./security/rtkit.nix ./security/setuid-wrappers.nix <ins> ./security/setcap-wrappers.nix </ins> ./security/sudo.nix ./services/amqp/activemq/default.nix ./services/amqp/rabbitmq.nix

<ins> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <dirent.h> #include <assert.h> #include <errno.h> #include <linux/capability.h> #include <sys/capability.h> #include <linux/prctl.h> #include <sys/prctl.h> #include <cap-ng.h> // 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; // 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 setn"); 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 setn"); 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 filen"); 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: %sn", 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 itn"); } else { set_ambient_cap(capnum); printf("raised %s into the Ambient capability setn", 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! assert(!make_caps_ambient(selfPath)); execve(sourceProg, argv, environ); fprintf(stderr, "%s: cannot run `%s': %sn", argv[0], sourceProg, strerror(errno)); exit(1); } </ins>

<ins> { config, lib, pkgs, ... }: with lib; with pkgs; let inherit (config.security) setcapWrapperDir; cfg = config.security.setcapCapabilities; # Produce a shell-code splice intended to be stitched into one of # the build or install phases within the `setcapWrapper` derivation. mkSetcapWrapper = { program, source ? null, ...}: '' if ! source=${if source != null then source else "$(readlink -f $(PATH=$SETCAP_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 -DSOURCE_PROG="$source" -DWRAPPER_DIR="${setcapWrapperDir}"  -lcap-ng -lcap ${./setcap-wrapper.c} -o $out/bin/${program}.wrapper ''; setcapWrappers = # This is only useful for Linux platforms and a kernel version of # 4.3 or greater assert pkgs.stdenv.isLinux; assert versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.3"; pkgs.stdenv.mkDerivation { name         = "setcap-wrapper"; unpackPhase  = "true"; buildInputs  = [ linuxHeaders_4_4 libcap 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} ''; }; in { options = { security.setcapCapabilities = mkOption { type    = types.listOf types.attrs; default = []; example = [ { program = "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.setcapWrapperDir = mkOption { type        = types.path; default     = "/var/setcap-wrappers"; internal    = true; description = '' This option defines the path to the setcap wrappers. It should generally not be overriden. ''; }; }; config = { # Make sure our setcap-wrapper dir exports to the PATH env # variable when initializing the shell environment.extraInit = '' # The setcap wrappers override other bin directories. export PATH="${config.security.setcapWrapperDir}:$PATH" ''; system.activationScripts.setcap = let setcapPrograms = cfg; configureSetcapWrapper = { program , capabilities , source ? null , owner  ? "nobody" , group  ? "nogroup" , setcap ? false }: '' mkdir -p ${setcapWrapperDir} cp ${setcapWrappers}/bin/${program}.wrapper ${setcapWrapperDir}/${program} # Prevent races chmod 0000 ${setcapWrapperDir}/${program} chown ${owner}.${group} ${setcapWrapperDir}/${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 '' ${libcap.out}/bin/setcap "cap_setpcap,${capabilities}" ${setcapWrapperDir}/${program} '' else "" } # Set the executable bit chmod u+rx,g+x,o+x ${setcapWrapperDir}/${program} ''; in stringAfter [ "users" ] '' # Look in the system path and in the default profile for # programs to be wrapped. SETCAP_PATH=${config.system.path}/bin:${config.system.path}/sbin # When a program is removed from the security.setcapCapabilities # 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 ${setcapWrapperDir}/* # Concatenate the generated shell slices to configure # wrappers for each program needing specialized capabilities. ${concatMapStrings configureSetcapWrapper setcapPrograms} ''; }; } </ins>

cat /etc/resolv.conf | resolvconf -m 1000 -a host fi <ins> # 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 </ins> # 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. exec {logOutFd}>&1 {logErrFd}>&2

<del> { config, lib, pkgs, utils, ... }: </del> <ins> { config, lib, pkgs, utils, stdenv, ... }: </ins> with lib; with utils;

(i: flip map [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${i.name}.proxy_arp" true)) )); <del> security.setuidPrograms = [ "ping" "ping6" ]; </del> <ins> # Capabilities won't work unless we have at-least a 4.3 Linux # kernel because we need the ambient capability security.setcapCapabilities = mkIf (versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.3") ( [ { program = "ping"; source  = "${pkgs.iputils.out}/bin/ping"; setcap  = true; capabilities = "cap_net_raw+p"; } { program = "ping6"; source  = "${pkgs.iputils.out}/bin/ping6"; setcap  = true; capabilities = "cap_net_raw+p"; } ] ); # If our linux kernel IS older than 4.3, let's setuid ping and ping6 security.setuidPrograms = mkIf (versionOlder (getVersion config.boot.kernelPackages.kernel) "4.3") ( [ "ping" "ping6" ] ); </ins> # Set the host and domain names in the activation script.  Don't # clear it if it's not configured in the NixOS configuration,