9

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.

6
  • The man page for capabilities is pretty invaluable when dealing with them. Look for the section called "Transformation of capabilities during execve" which explains the exact mechanics of how the new Permitted and Effective sets are populated from the Inheritable set.
    – Bratchley
    May 8, 2014 at 0:29
  • The UID of zero is special in capability calculation in that execve doesn't default to resetting the inheritable set. That's probably why the setuid is working. I would add the inheritable bit to the net binary and see if that fixes it.
    – Bratchley
    May 8, 2014 at 0:32
  • @Bratchley What is the inheritable set? The man page you link to mentions it several times, but I'm just looking for a quick summary of the important things in it -- like the PID, UID bit and other permissions, std in and out file descriptors, etc. Sep 20, 2018 at 14:37
  • @JosiahYoder They actually have an entry for it under "Thread capability sets" but basically it's the set of capabilities child processes can inherit from the parent process if the parent process has that capability. It's basically a way to prevent programs you kick off from having a capability without the parent process giving up the capability for itself.
    – Bratchley
    Sep 22, 2018 at 16:25
  • @Bratchley Thank you, but that didn't fully answer my question. I've started a new question to ask it clearly. The section "Capabilities list" in the man page you linked to answers my question partly, but I'm also interested in properties of a process that are inherited which are not capabilities. Sep 22, 2018 at 19:51

2 Answers 2

9

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.
1
  • 2
    You are mixing inheritable and permitted way in a dangerous may: By giving the file permitted permission, you give everyone that calls it this permission. Consider also the newer ambient set. Apr 29, 2020 at 9:09
1

I think you need both:

setcap cap_dac_override+pe ./registerdns
setcap cap_dac_override+i /usr/bin/net

The pe flags on registerdns say that running the program acquires the capability. The i flag on net says that it's allowed to inherit the capability from the calling program.

1
  • This is exactly what I've tried. I've edited my question to better reflect that. May 12, 2014 at 14:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .