Linux – Passing Capabilities Through exec

capabilitieslinuxsetcap

I'm trying to understand how Linux capabilities are passed to a process that has been exec()'d by another one. From what I've read, in order for a capability to be kept after exec, it must be in the inheritable set. What I am not sure of, though, is how that set gets populated.

My goal is to be able to run a program as a regular user that would normally require root. The capability it needs is cap_dac_override so it can read a private file. I do not want to give it any other capabilities.

Here's my wrapper:

#include <unistd.h>

int main(int argc, char *argv[]) {
    return execl("/usr/bin/net", "net", "ads", "dns", "register", "-P", NULL);
}

This works when I set the setuid permission on the resulting executable:

~ $ sudo chown root: ./registerdns
~ $ sudo chmod u+s ./registerdns
~ $ ./registerdns 
Successfully registered hostname with DNS

I would like to use capabilities instead of setuid, though. I've tried setting the cap_dac_override capability on the wrapper:

~ $ sudo setcap cap_dac_override=eip ./registerdns
~ $ ./registerdns 
Failed to open /var/lib/samba/private/secrets.tdb
ERROR: Unable to open secrets database

I've also tried setting the inheritable flag on the cap_dac_override capability for the net executable itself:

~ $ sudo setcap cap_dac_override=eip ./registerdns
~ $ sudo setcap cap_dac_override=i /usr/bin/net
~ $ ./registerdns 
Failed to open /var/lib/samba/private/secrets.tdb
ERROR: Unable to open secrets database

I need to use the wrapper to ensure that the capability is only available when using that exact set of arguments; the net program does several other things that could be dangerous to give users too broad of permissions on it.

I'm obviously misunderstanding how the inheritance works. I can't seem to figure out how to set up the wrapper to pass its capabilities along to the replacement process so it can use them. I've read the man page, and countless other documents on how it should work, and I thought I was doing what it describes.

Best Answer

It turns out that setting +i on the wrapper does not add the capability to the CAP_INHERITABLE set for the wrapper process, thus it is not passed through exec. I therefore had to manually add CAP_DAC_OVERRIDE to CAP_INHERITABLE before calling execl:

#include <sys/capability.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv[]) {
    cap_t caps = cap_get_proc();
    printf("Capabilities: %s\n", cap_to_text(caps, NULL));
    cap_value_t newcaps[1] = { CAP_DAC_OVERRIDE, };
    cap_set_flag(caps, CAP_INHERITABLE, 1, newcaps, CAP_SET);
    cap_set_proc(caps);
    printf("Capabilities: %s\n", cap_to_text(caps, NULL));
    cap_free(caps);
    return execl("/usr/bin/net", "net", "ads", "dns", "register", "-P", NULL);
}

In addition, I had to add cap_dac_override to the permitted file capabilities set on /usr/bin/net and set the effective bit:

~ $ sudo setcap cap_dac_override=p ./registerdns
~ $ sudo setcap cap_dac_override=ei /usr/bin/net
~ $ ./registerdns
Capabilities = cap_dac_override+p
Capabilities = cap_dac_override+ip
Successfully registered hostname with DNS

I think I now fully understand what's happening:

  1. The wrapper needs CAP_DAC_OVERRIDE in its permitted set so it can add it to its inheritable set.
  2. The wrapper's process inheritable set is different than its file inheritable set, so setting +i on the file is useless; the wrapper must explicitly add CAP_DAC_OVERRIDE to CAP_INHERITABLE using cap_set_flag/cap_set_proc.
  3. The net file needs to have CAP_DAC_OVERRIDE in its inheritable set so that it can in fact inherit the capability from the wrapper into its CAP_PERMITTED set. It also needs the effective bit to be set so that it will be automatically promoted to CAP_EFFECTIVE.