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:
- The wrapper needs
CAP_DAC_OVERRIDE
in its permitted set so it can add it to its inheritable set.
- 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
.
- 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
.
Capabilities are properties of processes. Traditionally there are three sets:
- Permitted capabilities (p): capabilities that may be "activated" in the current process.
- Effective capabilities (e): capabilities that are currently usable in the current process.
- Inheritable capabilities (i): file capabilities that may be inherited.
Programs run as root always have full permitted and effective capabilities, so "adding" more capabilities has no noticeable effect. (The inheritable capabilities set is normally empty.) With setcap cap_net_raw+ep ping
you enable these capabilities by default for any user running this program.
Unfortunately these capabilities are bound to the executed file and are not retained after executing a new child process. Linux 4.3 introduced Ambient capabilities which allows capabilities to be inherited by child processes. (See also Transformation of capabilities during execve() in capabilities(7).)
While playing with capabilities, note these pitfalls:
- When changing the user from root to non-root, the effective and permitted capabilities are cleared (see Effect of user ID changes on capabilities in capabilities(7)). You can use the
--keep=1
option of capsh
to avoid clearing the sets.
- The ambient capabilities set is cleared when changing the user or group IDs. Solution: add the ambient capabilities after changing the user ID, but before executing a child process.
- A capability can only be added to the ambient capabilities set if it is already in both the permitted and inheritable capabilities set.
Since libcap 2.26, the capsh
program gained the ability to modify ambient capabilities via options such as --addamb
(commit). Note that the options order is significant. Example usage:
sudo capsh --caps="cap_net_raw+eip cap_setpcap,cap_setuid,cap_setgid+ep" \
--keep=1 --user=nobody --addamb=cap_net_raw -- \
-c "./ping -c1 127.0.0.1"
Tip: you can add the --print
option anywhere in the capsh
command line and see its current capabilities state.
Note: cap_setpcap
is needed for --addamb
while cap_setuid,cap_setgid
are needed for the --user
option.
Best Answer
Setting capability on the script will not be effective. It's the similar situation as not working
setuid
bit on script. Similar as in the latter case it's the implementation of howexecve
handles shebang and the security reasoning behind it (for details see: Allow setuid on shell scripts).I think you have these options
set the capabilities on interpreter itself (actually rather a copy of it)
write a wrapper executable which will have a hardcoded logic to execute your script, and set desired capabilities on this executable
chroot
one might missuse such wrapperIn both cases you would have to make sure capabilities set will survive
execve
by settinginheritable
flag. You might also usepam_cap
distributed withlibcap
usually, to actually activate desired capabilities by configuration only for selected users.And in general you want to make sure nobody is able to modify behavior of your interpreter by changing environment eg.
PYTHON_PATH
or something similar.