Search code examples
c++shelltrafficlinux-capabilities

Configuring permissions to invoke tc qdisc shell commands requiring CAP_NET_ADMIN in C/C++ program


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.


Solution

  • 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.