linuxlinux-kernellinux-capabilities

What is the Linux capability(7) to write to /proc/sys/net/ipv6/conf/$IF/disable_ipv6?


I've got a test that I'm writing for a larger program that needs to (1) create a tap device, (2) bind a raw socket, and (3) make a sysctl to disable IPv6. I'm writing to find the set of Linux capabilities to do all of these things so I don't have to run the test as root. The first two are easy: CAP_NET_ADMIN and CAP_NET_RAW, but try as I might, I cannot seem to figure out the capability needed to run:

echo 1 > /proc/sys/net/ipv6/conf/$if/disable_ipv6   # e.g., for 'if=eth0',etc.

I've tried CAP_SYS_ADMIN and CAP_DAC_OVERRIDE and neither of those are sufficient. I've tried to read through the kernel source but am not making headway, so I'm going to cop out and ask for help.

Thank you in advance!

[edit]

$ getcap ./target/debug/deps/tap_tests-e43c717a6e86b805
./target/debug/deps/tap_tests-e43c717a6e86b805 
cap_net_admin,cap_net_raw,cap_sys_admin=eip
$ ./target/debug/deps/tap_tests-e43c717a6e86b805
[snip]
--- test_conntrack_active_probe stdout ----
thread 'test_conntrack_active_probe' panicked at 'safe_run_command FAILED: 'sysctl -w net.ipv6.conf.testtap1.disable_ipv6=1' command returned stderr 'Ok(
    "sysctl: permission denied on key \"net.ipv6.conf.testtap1.disable_ipv6\"\n",
)'', tests/tap_tests.rs:275:13

Also, I had to do this to debug:

$ cp `which strace` ./strace
$ setcap CAP_NET_ADMIN=epi ./strace
$ ./strace -f <test>     # normal strace doesn't inherit the test's capabilities

Posting here in case it helps others.

[edit2]

It looks like this is the core problem/solution: https://unix.stackexchange.com/questions/128394/passing-capabilities-through-exec


Solution

  • It looks like only CAP_NET_ADMIN is necessary.

    We can verify that using a simple C program that, given a list of interfaces as arguments, sets the disable_ipv6 sysctl for each interface:

    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <fcntl.h>
    
    #define SYSCTL_TEMPLATE "/proc/sys/net/ipv6/conf/%s/disable_ipv6"
    
    int main(int argc, char **argv) {
        int template_len = strlen(SYSCTL_TEMPLATE);
    
        for (int i=1; i<argc; i++) {
            int path_len, fd, nb;
            char path[1024];
    
            printf("interface name: %s\n", argv[i]);
            path_len = template_len + strlen(argv[i]);
            if (path_len > 1024) {
                fprintf(stderr, "path too long\n");
                exit(1);
            }
    
            sprintf(path, SYSCTL_TEMPLATE, argv[i]);
            
            if ((fd = open(path, O_WRONLY)) < 0) {
                perror("open");
                exit(1);
            }
    
            if ((nb = write(fd, "1", 1)) < 0) {
                perror("write");
                exit(1);
            }
        }
        return 0;
    }
    

    I'm going to be using an interface named vlan1 as my test interface; initially, it looks like:

    $ sysctl net.ipv6.conf.vlan1.disable_ipv6
    net.ipv6.conf.vlan1.disable_ipv6 = 0
    

    If I run the compiled binary as a normal user, it fails with a "permission denied" error:

    $ ./disable_ipv6 vlan1
    interface name: vlan1
    open: Permission denied
    

    If I set the CAP_NET_ADMIN capability on this binary:

    $ sudo setcap cap_net_admin+eip disable_ipv6
    

    And then re-run it, it succeeds:

    $ ./disable_ipv6 vlan1
    interface name: vlan1
    

    After which we can see the value of the sysctl has been modified:

    $ sysctl net.ipv6.conf.vlan1.disable_ipv6
    net.ipv6.conf.vlan1.disable_ipv6 = 1
    

    If your goal is explicitly to set this sysctl using echo, then you'll need to arrange to run a shell with the necessary privileges. It looks like we can do this using the --addamb option to capsh (to add the capability to the ambient capability set):

    $ sudo env HOME=$HOME capsh --user=$USER --inh=cap_net_admin --addamb=cap_net_admin --
    

    This gives us the necessary capability set:

    $ getpcaps $$
    1496078: cap_net_admin=eip
    

    And allows us to modify the desired sysctl without errors:

    $ echo 1 > /proc/sys/net/ipv6/conf/vlan1/disable_ipv6
    $ echo 0 > /proc/sys/net/ipv6/conf/vlan1/disable_ipv6