Mitja Rislakki
Mitja Rislakki

Reputation: 1

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.

Upvotes: 0

Views: 96

Answers (1)

Mitja Rislakki
Mitja Rislakki

Reputation: 1

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.

Upvotes: 0

Related Questions