I have a C++ program that intends to set queuing disciplines by invoking (iproute2) tc shell commands.
Example code:
#include <iostream>
#include <cstdlib>
int main() {
const char* cmd = "tc qdisc add dev veno1 root fq_codel";
int result = std::system(cmd);
if (result == 0) {
std::cout << "qdisc configured successfully" << std::endl;
} else {
std::cerr << "failed to configure qdisc" << std::endl;
}
return 0;
}
After compiling and running the program, the output is
RTNETLINK answers: Operation not permitted
Failed to configure qdisc
Which makes sense as tc qdisc add is privileged. I'm trying to find a way to give the program privileges to manage qdiscs without running as sudo.
tc qdisc add
requires CAP_NET_ADMIN, so the following works:
sudo setcap cap_net_admin+ep /sbin/tc
but this obviously isn't limited to just the program as preferred. However, sudo setcap cap_net_admin+eip ./myprogram
does not work, resulting in the same output as above, implying the capability isn't inherited/passed to tc
in the system() -shell. Is there some way configure the capabilities or otherwise execute the command inside the program such that tc
has CAP_NET_ADMIN capabilities without sudo?
Sidenote: The intended use is with TAPRIO qdiscs and makes up a small part of program operation so I would like to avoid doing it in libnl if possible.
Thanks @3CxEZiVlQ for pointing me in the right direction. Here are some working examples to set & clear the inheritable capability and ambient capability set, following:
Ambient (since Linux 4.3)
This is a set of capabilities that are preserved across an execve(2) of a program that is not privileged. The ambient capability set obeys the invariant that no capability can ever be ambient if it is not both permitted and inheritable. The ambient capability set can be directly modified using prctl(2). Ambient capabilities are automatically lowered if either of the corresponding permitted or inheritable capabilities is lowered.
#include <iostream>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <unistd.h> // For sleep
void setInheritance() {
cap_t caps = cap_get_proc();
if (caps == NULL) throw std::runtime_error("Failed: cap_get_proc()");
cap_value_t capList[] = {CAP_NET_ADMIN};
if (cap_set_flag(caps, CAP_INHERITABLE, 1, capList, CAP_SET) == -1)
throw std::runtime_error("Failed: cap_set_flag() set inheritable");
if (cap_set_proc(caps) == -1) throw std::runtime_error("Failed: cap_set_proc()");
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_ADMIN, 0, 0) == -1)
throw std::runtime_error("Failed: prctl() ambient raise");
caps = cap_get_proc();
if (caps == NULL) throw std::runtime_error("Failed: cap_get_proc()");
std:: cout << "Capabilities after setInheritance(): " << cap_to_text(caps, NULL) << '\n';
}
void clearInheritance() {
cap_t caps = cap_get_proc();
if (caps == NULL) throw std::runtime_error("Failed: cap_get_proc()");
cap_value_t capList[] = {CAP_NET_ADMIN};
if (cap_set_flag(caps, CAP_INHERITABLE, 1, capList, CAP_CLEAR) == -1)
throw std::runtime_error("Failed: cap_set_flag() clear inheritable");
if (cap_set_proc(caps) == -1) throw std::runtime_error("Failed: cap_set_proc()");
}
int main() {
setInheritance(); // Add CAP_NET_ADMIN inheritance to capabilities
if (system("tc qdisc add dev veno1 root fq_codel") == 0) {
std::cout << "qdisc configured successfully" << std::endl;
} else {
std::cerr << "failed to configure qdisc" << std::endl;
}
sleep(2);
if (system("tc qdisc del dev veno1 root") == 0) {
std::cout << "qdisc deleted successfully" << std::endl;
} else {
std::cerr << "failed to delete qdisc" << std::endl;
}
sleep(2);
clearInheritance(); // Remove CAP_NET_ADMIN from capabilities, removing ambient
// Should fail - Operation not permitted
if (system("tc qdisc add dev veno1 root fq_codel") == 0) {
std::cout << "qdisc configured successfully" << std::endl;
} else {
std::cerr << "failed to configure qdisc" << std::endl;
}
return 0;
}
Running the program:
$ g++ setqdisc.cc -lcap -o setqdisc
$ sudo setcap cap_net_admin+p ./setqdisc
$ ./setqdisc
Capabilities after setInheritance(): cap_net_admin=ip
qdisc configured successfully
qdisc deleted successfully
RTNETLINK answers: Operation not permitted
failed to configure qdisc
Note that it's enough to give the binary Permitted capability (setcap
with +p
)
EDIT:
cap_t
allocated by cap_get_proc()
should be freed by calling cap_free()
per documentation:
cap_get_proc() allocates a capability state in working storage, sets its state to that of the calling process, and returns a pointer to this newly created capability state. The caller should free any releasable memory, when the capability state in working storage is no longer required, by calling cap_free() with the cap_t as an argument.