Message ID | 20210505081845.7024-6-mdoucha@suse.cz |
---|---|
State | Changes Requested |
Headers | show |
Series | [v3,1/6] Add SAFE_REALLOC() helper function to LTP library | expand |
> Fixes #742 LGTM. Few unimportant comments below. Reviewed-by: Petr Vorel <pvorel@suse.cz> ... > diff --git a/testcases/cve/cve-2020-25705.c b/testcases/cve/cve-2020-25705.c > new file mode 100644 > index 000000000..7d6bbafa8 > --- /dev/null > +++ b/testcases/cve/cve-2020-25705.c > @@ -0,0 +1,262 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Copyright (C) 2020 SUSE LLC > + * Author: Nicolai Stange <nstange@suse.de> > + * LTP port: Martin Doucha <mdoucha@suse.cz> > + * > + * CVE-2020-25705 > + * > + * Test of ICMP rate limiting behavior that may be abused for DNS cache > + * poisoning attack. Send a few batches of 100 packets to a closed UDP port > + * and count the ICMP errors. If the number of errors is always the same > + * for each batch (not randomized), the system is vulnerable. Send packets > + * from multiple IP addresses to bypass per-address ICMP throttling. We probably turn this into docparse during merge. > + * > + * Fixed in: > + * > + * commit b38e7819cae946e2edf869e604af1e65a5d241c5 > + * Author: Eric Dumazet <edumazet@google.com> > + * Date: Thu Oct 15 11:42:00 2020 -0700 > + * > + * icmp: randomize the global rate limiter > + */ > + > +#include <sys/socket.h> > +#include <netinet/in.h> > +#include <arpa/inet.h> > +#include <linux/if_addr.h> > +#include <linux/errqueue.h> #include <time.h> to fix build failure on MUSL: cve-2020-25705.c:262:1: warning: missing initializer for field 'needs_cmds' of 'struct tst_test' [-Wmissing-field-initializers] https://travis-ci.org/github/pevik/ltp/jobs/769551152 > +#include <sched.h> > +#include <limits.h> > +#include "tst_test.h" > +#include "tst_netdevice.h" > + > +#define DSTADDR 0xfa444e02 /* 250.68.78.2 */ > +#define SRCADDR_BASE 0xfa444e41 /* 250.68.78.65 */ > +#define SRCADDR_COUNT 50 > +#define BATCH_COUNT 8 > +#define BUFSIZE 1024 > + > +static int parentns = -1, childns = -1; > +static int fds[SRCADDR_COUNT]; > + > +static void setup(void) > +{ ... > + /* > + * Create network namespace to hide the destination interface from > + * the test process. > + */ > + parentns = SAFE_OPEN("/proc/self/ns/net", O_RDONLY); > + SAFE_UNSHARE(CLONE_NEWNET); > + > + /* Do NOT close this FD, or both interfaces will be destroyed */ > + childns = SAFE_OPEN("/proc/self/ns/net", O_RDONLY); > + > + /* Configure child namespace */ > + CREATE_VETH_PAIR("ltp_veth1", "ltp_veth2"); > + addr = DSTADDR; > + NETDEV_ADD_ADDRESS_INET("ltp_veth2", htonl(addr), 26, > + IFA_F_NOPREFIXROUTE); I wonder if it'd be useful *later* (not bothering with it now) to allow tests just declare .needs_netdevice = 1 and have generic network setup done (similarly it's done in tst_net.sh). Or just define addresses a prefixes and do library to do the setup. > + NETDEV_SET_STATE("ltp_veth2", 1); > + NETDEV_ADD_ROUTE_INET("ltp_veth2", 0, 0, htonl(0xfa444e40), 26, nit: maybe define 0xfa444e40 (and 0xfa444e00) and 26 as constants? > + 0); > + > + /* Configure parent namespace */ > + NETDEV_CHANGE_NS_FD("ltp_veth1", parentns); > + SAFE_SETNS(parentns, CLONE_NEWNET); > + addr = SRCADDR_BASE; /* 250.68.78.65 */ nit: maybe repeating the address in the comment is not needed. > + > + for (i = 0; i < SRCADDR_COUNT; i++, addr++) { > + NETDEV_ADD_ADDRESS_INET("ltp_veth1", htonl(addr), 26, > + IFA_F_NOPREFIXROUTE); > + } > + > + NETDEV_SET_STATE("ltp_veth1", 1); > + NETDEV_ADD_ROUTE_INET("ltp_veth1", 0, 0, htonl(0xfa444e00), 26, 0); > + SAFE_FILE_PRINTF("/proc/sys/net/ipv4/conf/ltp_veth1/forwarding", "1"); > + > + /* Open test sockets */ > + for (i = 0; i < SRCADDR_COUNT; i++) { > + ipaddr.sin_addr.s_addr = htonl(SRCADDR_BASE + i); > + fds[i] = SAFE_SOCKET(AF_INET, SOCK_DGRAM, 0); > + SAFE_SETSOCKOPT_INT(fds[i], IPPROTO_IP, IP_RECVERR, 1); > + SAFE_BIND(fds[i], (struct sockaddr *)&ipaddr, sizeof(ipaddr)); > + } > +} > + > +static int count_icmp_errors(int fd) > +{ > + int error_count = 0; > + ssize_t len; > + char msgbuf[BUFSIZE], errbuf[BUFSIZE]; > + struct sockaddr_in addr; > + struct cmsghdr *cmsg; > + struct sock_extended_err exterr; > + struct iovec iov = { > + .iov_base = msgbuf, > + .iov_len = BUFSIZE > + }; > + > + while (1) { > + struct msghdr msg = { > + .msg_name = (struct sockaddr *)&addr, > + .msg_namelen = sizeof(addr), > + .msg_iov = &iov, > + .msg_iovlen = 1, > + .msg_flags = 0, > + .msg_control = errbuf, > + .msg_controllen = BUFSIZE > + }; > + > + memset(errbuf, 0, BUFSIZE); > + errno = 0; > + len = recvmsg(fd, &msg, MSG_ERRQUEUE); > + > + if (len == -1) { > + if (errno == EWOULDBLOCK || errno == EAGAIN) > + break; > + > + tst_brk(TBROK | TERRNO, "recvmsg() failed"); > + } > + > + if (len < 0) { > + tst_brk(TBROK | TERRNO, > + "Invalid recvmsg() return value %zd", len); > + } > + > + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; > + cmsg = CMSG_NXTHDR(&msg, cmsg)) { > + if (cmsg->cmsg_level != SOL_IP) > + continue; > + > + if (cmsg->cmsg_type != IP_RECVERR) > + continue; > + > + memcpy(&exterr, CMSG_DATA(cmsg), sizeof(exterr)); > + > + if (exterr.ee_origin != SO_EE_ORIGIN_ICMP) > + tst_brk(TBROK, "Unexpected non-ICMP error"); > + > + if (exterr.ee_errno != ECONNREFUSED) { > + TST_ERR = exterr.ee_errno; > + tst_brk(TBROK | TTERRNO, > + "Unexpected ICMP error"); > + } > + > + error_count++; > + } > + } > + > + return error_count; > +} FYI I tested the test on several VM. Very old kernel detects problem only on more runs. But given it's 3.16 (and b38e7819cae9 is a fix for 4cdf507d5452 from v3.18-rc1 we can ignore this). Kind regards, Petr # ./cve-2020-25705 tst_kconfig.c:64: TINFO: Parsing kernel config '/boot/config-3.16.0-11-amd64' tst_test.c:1313: TINFO: Timeout per run is 0h 05m 00s cve-2020-25705.c:217: TINFO: Batch 0: Got 85 ICMP errors cve-2020-25705.c:221: TINFO: Batch 1: Got 100 ICMP errors cve-2020-25705.c:230: TPASS: ICMP rate limit is randomized -i2 # ./cve-2020-25705 -i2 tst_kconfig.c:64: TINFO: Parsing kernel config '/boot/config-3.16.0-11-amd64' tst_test.c:1313: TINFO: Timeout per run is 0h 05m 00s cve-2020-25705.c:217: TINFO: Batch 0: Got 85 ICMP errors cve-2020-25705.c:221: TINFO: Batch 1: Got 100 ICMP errors cve-2020-25705.c:230: TPASS: ICMP rate limit is randomized cve-2020-25705.c:217: TINFO: Batch 0: Got 100 ICMP errors cve-2020-25705.c:221: TINFO: Batch 1: Got 100 ICMP errors cve-2020-25705.c:221: TINFO: Batch 2: Got 100 ICMP errors cve-2020-25705.c:221: TINFO: Batch 3: Got 100 ICMP errors cve-2020-25705.c:221: TINFO: Batch 4: Got 100 ICMP errors cve-2020-25705.c:221: TINFO: Batch 5: Got 100 ICMP errors cve-2020-25705.c:221: TINFO: Batch 6: Got 100 ICMP errors cve-2020-25705.c:221: TINFO: Batch 7: Got 100 ICMP errors cve-2020-25705.c:226: TFAIL: ICMP rate limit not randomized, system is vulnerable HINT: You _MAY_ be missing kernel fixes, see: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b38e7819cae9 HINT: You _MAY_ be vulnerable to CVE(s), see: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-25705 Summary: passed 1 failed 1 broken 0 skipped 0 warnings 0
On 05. 05. 21 12:04, Petr Vorel wrote: > I wonder if it'd be useful *later* (not bothering with it now) to allow tests > just declare .needs_netdevice = 1 and have generic network setup done (similarly > it's done in tst_net.sh). Or just define addresses a prefixes and do library to > do the setup. Sounds like a good idea. > >> + NETDEV_SET_STATE("ltp_veth2", 1); >> + NETDEV_ADD_ROUTE_INET("ltp_veth2", 0, 0, htonl(0xfa444e40), 26, > nit: maybe define 0xfa444e40 (and 0xfa444e00) and 26 as constants? >> + 0); >> + >> + /* Configure parent namespace */ >> + NETDEV_CHANGE_NS_FD("ltp_veth1", parentns); >> + SAFE_SETNS(parentns, CLONE_NEWNET); >> + addr = SRCADDR_BASE; /* 250.68.78.65 */ > nit: maybe repeating the address in the comment is not needed. Yes, I should fix this and the <time.h> issue. Please push everything up to patch 5 and then I'll resubmit v4 just for this patch. > FYI I tested the test on several VM. Very old kernel detects problem only on > more runs. But given it's 3.16 (and b38e7819cae9 is a fix for 4cdf507d5452 from > v3.18-rc1 we can ignore this). Pass is expected here. Vanilla v3.16 AFAIK does not have the global ICMP rate limiter which introduced the real vulnerability in the first place.
Hi Martin, > Yes, I should fix this and the <time.h> issue. Please push everything up > to patch 5 and then I'll resubmit v4 just for this patch. NOTE: <time.h> must be included *before* <linux/errqueue.h> (maybe bug in musl headers). I suppose Cyril will merge these 4 patches soon. > > FYI I tested the test on several VM. Very old kernel detects problem only on > > more runs. But given it's 3.16 (and b38e7819cae9 is a fix for 4cdf507d5452 from > > v3.18-rc1 we can ignore this). > Pass is expected here. Vanilla v3.16 AFAIK does not have the global ICMP > rate limiter which introduced the real vulnerability in the first place. Sure. I was just surprised to get different result on -i1 and on -i >= 2. Kind regards, Petr
Hi! > diff --git a/testcases/cve/cve-2020-25705.c b/testcases/cve/cve-2020-25705.c Can we please name the file icmp_rate_limit01.c or something more human readable than the cve-2020-25705? > new file mode 100644 > index 000000000..7d6bbafa8 > --- /dev/null > +++ b/testcases/cve/cve-2020-25705.c > @@ -0,0 +1,262 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Copyright (C) 2020 SUSE LLC > + * Author: Nicolai Stange <nstange@suse.de> > + * LTP port: Martin Doucha <mdoucha@suse.cz> > + * > + * CVE-2020-25705 > + * > + * Test of ICMP rate limiting behavior that may be abused for DNS cache > + * poisoning attack. Send a few batches of 100 packets to a closed UDP port > + * and count the ICMP errors. If the number of errors is always the same > + * for each batch (not randomized), the system is vulnerable. Send packets > + * from multiple IP addresses to bypass per-address ICMP throttling. > + * > + * Fixed in: > + * > + * commit b38e7819cae946e2edf869e604af1e65a5d241c5 > + * Author: Eric Dumazet <edumazet@google.com> > + * Date: Thu Oct 15 11:42:00 2020 -0700 > + * > + * icmp: randomize the global rate limiter > + */ > + > +#include <sys/socket.h> > +#include <netinet/in.h> > +#include <arpa/inet.h> > +#include <linux/if_addr.h> > +#include <linux/errqueue.h> > + > +#include <sched.h> > +#include <limits.h> > +#include "tst_test.h" > +#include "tst_netdevice.h" > + > +#define DSTADDR 0xfa444e02 /* 250.68.78.2 */ > +#define SRCADDR_BASE 0xfa444e41 /* 250.68.78.65 */ > +#define SRCADDR_COUNT 50 > +#define BATCH_COUNT 8 > +#define BUFSIZE 1024 > + > +static int parentns = -1, childns = -1; > +static int fds[SRCADDR_COUNT]; > + > +static void setup(void) > +{ > + struct sockaddr_in ipaddr = { .sin_family = AF_INET }; > + uint32_t addr; > + int i; > + int real_uid = getuid(); > + int real_gid = getgid(); > + > + for (i = 0; i < SRCADDR_COUNT; i++) > + fds[i] = -1; > + > + SAFE_UNSHARE(CLONE_NEWUSER); > + SAFE_UNSHARE(CLONE_NEWNET); > + SAFE_FILE_PRINTF("/proc/self/setgroups", "deny"); > + SAFE_FILE_PRINTF("/proc/self/uid_map", "0 %d 1\n", real_uid); > + SAFE_FILE_PRINTF("/proc/self/gid_map", "0 %d 1\n", real_gid); > + > + /* > + * Create network namespace to hide the destination interface from > + * the test process. > + */ > + parentns = SAFE_OPEN("/proc/self/ns/net", O_RDONLY); > + SAFE_UNSHARE(CLONE_NEWNET); > + > + /* Do NOT close this FD, or both interfaces will be destroyed */ > + childns = SAFE_OPEN("/proc/self/ns/net", O_RDONLY); > + > + /* Configure child namespace */ > + CREATE_VETH_PAIR("ltp_veth1", "ltp_veth2"); > + addr = DSTADDR; > + NETDEV_ADD_ADDRESS_INET("ltp_veth2", htonl(addr), 26, > + IFA_F_NOPREFIXROUTE); > + NETDEV_SET_STATE("ltp_veth2", 1); > + NETDEV_ADD_ROUTE_INET("ltp_veth2", 0, 0, htonl(0xfa444e40), 26, > + 0); > + > + /* Configure parent namespace */ > + NETDEV_CHANGE_NS_FD("ltp_veth1", parentns); > + SAFE_SETNS(parentns, CLONE_NEWNET); > + addr = SRCADDR_BASE; /* 250.68.78.65 */ > + > + for (i = 0; i < SRCADDR_COUNT; i++, addr++) { > + NETDEV_ADD_ADDRESS_INET("ltp_veth1", htonl(addr), 26, > + IFA_F_NOPREFIXROUTE); > + } > + > + NETDEV_SET_STATE("ltp_veth1", 1); > + NETDEV_ADD_ROUTE_INET("ltp_veth1", 0, 0, htonl(0xfa444e00), 26, 0); > + SAFE_FILE_PRINTF("/proc/sys/net/ipv4/conf/ltp_veth1/forwarding", "1"); > + > + /* Open test sockets */ > + for (i = 0; i < SRCADDR_COUNT; i++) { > + ipaddr.sin_addr.s_addr = htonl(SRCADDR_BASE + i); > + fds[i] = SAFE_SOCKET(AF_INET, SOCK_DGRAM, 0); > + SAFE_SETSOCKOPT_INT(fds[i], IPPROTO_IP, IP_RECVERR, 1); > + SAFE_BIND(fds[i], (struct sockaddr *)&ipaddr, sizeof(ipaddr)); > + } > +} > + > +static int count_icmp_errors(int fd) > +{ > + int error_count = 0; > + ssize_t len; > + char msgbuf[BUFSIZE], errbuf[BUFSIZE]; > + struct sockaddr_in addr; > + struct cmsghdr *cmsg; > + struct sock_extended_err exterr; > + struct iovec iov = { > + .iov_base = msgbuf, > + .iov_len = BUFSIZE > + }; > + > + while (1) { > + struct msghdr msg = { > + .msg_name = (struct sockaddr *)&addr, > + .msg_namelen = sizeof(addr), > + .msg_iov = &iov, > + .msg_iovlen = 1, > + .msg_flags = 0, > + .msg_control = errbuf, > + .msg_controllen = BUFSIZE > + }; > + > + memset(errbuf, 0, BUFSIZE); > + errno = 0; > + len = recvmsg(fd, &msg, MSG_ERRQUEUE); > + > + if (len == -1) { > + if (errno == EWOULDBLOCK || errno == EAGAIN) > + break; > + > + tst_brk(TBROK | TERRNO, "recvmsg() failed"); > + } > + > + if (len < 0) { > + tst_brk(TBROK | TERRNO, > + "Invalid recvmsg() return value %zd", len); > + } > + > + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; > + cmsg = CMSG_NXTHDR(&msg, cmsg)) { > + if (cmsg->cmsg_level != SOL_IP) > + continue; > + > + if (cmsg->cmsg_type != IP_RECVERR) > + continue; > + > + memcpy(&exterr, CMSG_DATA(cmsg), sizeof(exterr)); > + > + if (exterr.ee_origin != SO_EE_ORIGIN_ICMP) > + tst_brk(TBROK, "Unexpected non-ICMP error"); > + > + if (exterr.ee_errno != ECONNREFUSED) { > + TST_ERR = exterr.ee_errno; > + tst_brk(TBROK | TTERRNO, > + "Unexpected ICMP error"); > + } > + > + error_count++; > + } > + } > + > + return error_count; > +} > + > +static int packet_batch(const struct sockaddr *addr, socklen_t addrsize) > +{ > + int i, j, error_count = 0; > + char data = 0; > + > + for (i = 0; i < SRCADDR_COUNT; i++) { > + for (j = 0; j < 2; j++) { > + error_count += count_icmp_errors(fds[i]); > + TEST(sendto(fds[i], &data, sizeof(data), 0, addr, > + addrsize)); > + > + if (TST_RET == -1) { > + if (TST_ERR == ECONNREFUSED) { > + j--; /* flush ICMP errors and retry */ > + continue; > + } > + > + tst_brk(TBROK | TTERRNO, "sento() failed"); > + } > + > + if (TST_RET < 0) { > + tst_brk(TBROK | TTERRNO, > + "Invalid sento() return value %ld", > + TST_RET); > + } > + } > + } > + > + /* Wait and collect pending ICMP errors */ > + sleep(2); Can we do something better than sleep() here? Maybe just loop over the loop that reads errors with some short sleep and exit if we haven't got any new errors for a while? Something as: unsigned int timeout_ms = 2000; unsigned int no_changes_ms = 0; while (timeout_ms) { int last_err_cnt = error_count; for (i = 0; i < SRCADDR_COUNT; i++) error_count += count_icmp_errors(fds[i]); if (last_err_cnt == error_count) no_changes_ms++; else no_changes_ms = 0; if (no_changes_ms > 500) break; usleep(1000); timeout_ms--; } > + for (i = 0; i < SRCADDR_COUNT; i++) > + error_count += count_icmp_errors(fds[i]); > + > + return error_count; > +} > + > +static void run(void) > +{ > + int i, errors_baseline, errors; > + struct sockaddr_in addr = { > + .sin_family = AF_INET, > + .sin_port = TST_GET_UNUSED_PORT(AF_INET, SOCK_DGRAM), > + .sin_addr = { htonl(DSTADDR) } > + }; > + > + errors_baseline = packet_batch((struct sockaddr *)&addr, sizeof(addr)); > + errors = errors_baseline; > + tst_res(TINFO, "Batch 0: Got %d ICMP errors", errors); > + > + for (i = 1; i < BATCH_COUNT && errors == errors_baseline; i++) { > + errors = packet_batch((struct sockaddr *)&addr, sizeof(addr)); > + tst_res(TINFO, "Batch %d: Got %d ICMP errors", i, errors); > + } > + > + if (errors == errors_baseline) { > + tst_res(TFAIL, > + "ICMP rate limit not randomized, system is vulnerable"); > + return; > + } > + > + tst_res(TPASS, "ICMP rate limit is randomized"); > +} > + > +static void cleanup(void) > +{ > + int i; > + > + for (i = 0; i < SRCADDR_COUNT; i++) > + if (fds[i] >= 0) > + SAFE_CLOSE(fds[i]); > + > + if (childns >= 0) > + SAFE_CLOSE(childns); > + > + if (parentns >= 0) > + SAFE_CLOSE(parentns); > +} > + > +static struct tst_test test = { > + .test_all = run, > + .setup = setup, > + .cleanup = cleanup, > + .needs_kconfigs = (const char *[]) { > + "CONFIG_USER_NS=y", > + "CONFIG_NET_NS=y", > + NULL > + }, > + .tags = (const struct tst_tag[]) { > + {"linux-git", "b38e7819cae9"}, > + {"CVE", "2020-25705"}, > + {} > + } > +}; > -- > 2.31.1 > > > -- > Mailing list info: https://lists.linux.it/listinfo/ltp
Hello, Michal and Nicolai, please see the question about sleep() near the end of this e-mail. This is a new LTP test for the global ICMP rate limiter exploit that can enable the SADDNS attack. On 05. 05. 21 15:06, Cyril Hrubis wrote: > Hi! >> diff --git a/testcases/cve/cve-2020-25705.c b/testcases/cve/cve-2020-25705.c > > Can we please name the file icmp_rate_limit01.c or something more human > readable than the cve-2020-25705? I'll rename it in v4. >> new file mode 100644 >> index 000000000..7d6bbafa8 >> --- /dev/null >> +++ b/testcases/cve/cve-2020-25705.c >> @@ -0,0 +1,262 @@ >> +// SPDX-License-Identifier: GPL-2.0-or-later >> +/* >> + * Copyright (C) 2020 SUSE LLC >> + * Author: Nicolai Stange <nstange@suse.de> >> + * LTP port: Martin Doucha <mdoucha@suse.cz> >> + * >> + * CVE-2020-25705 >> + * >> + * Test of ICMP rate limiting behavior that may be abused for DNS cache >> + * poisoning attack. Send a few batches of 100 packets to a closed UDP port >> + * and count the ICMP errors. If the number of errors is always the same >> + * for each batch (not randomized), the system is vulnerable. Send packets >> + * from multiple IP addresses to bypass per-address ICMP throttling. >> + * >> + * Fixed in: >> + * >> + * commit b38e7819cae946e2edf869e604af1e65a5d241c5 >> + * Author: Eric Dumazet <edumazet@google.com> >> + * Date: Thu Oct 15 11:42:00 2020 -0700 >> + * >> + * icmp: randomize the global rate limiter >> + */ >> + >> +#include <sys/socket.h> >> +#include <netinet/in.h> >> +#include <arpa/inet.h> >> +#include <linux/if_addr.h> >> +#include <linux/errqueue.h> >> + >> +#include <sched.h> >> +#include <limits.h> >> +#include "tst_test.h" >> +#include "tst_netdevice.h" >> + >> +#define DSTADDR 0xfa444e02 /* 250.68.78.2 */ >> +#define SRCADDR_BASE 0xfa444e41 /* 250.68.78.65 */ >> +#define SRCADDR_COUNT 50 >> +#define BATCH_COUNT 8 >> +#define BUFSIZE 1024 >> + >> +static int parentns = -1, childns = -1; >> +static int fds[SRCADDR_COUNT]; >> + >> +static void setup(void) >> +{ >> + struct sockaddr_in ipaddr = { .sin_family = AF_INET }; >> + uint32_t addr; >> + int i; >> + int real_uid = getuid(); >> + int real_gid = getgid(); >> + >> + for (i = 0; i < SRCADDR_COUNT; i++) >> + fds[i] = -1; >> + >> + SAFE_UNSHARE(CLONE_NEWUSER); >> + SAFE_UNSHARE(CLONE_NEWNET); >> + SAFE_FILE_PRINTF("/proc/self/setgroups", "deny"); >> + SAFE_FILE_PRINTF("/proc/self/uid_map", "0 %d 1\n", real_uid); >> + SAFE_FILE_PRINTF("/proc/self/gid_map", "0 %d 1\n", real_gid); >> + >> + /* >> + * Create network namespace to hide the destination interface from >> + * the test process. >> + */ >> + parentns = SAFE_OPEN("/proc/self/ns/net", O_RDONLY); >> + SAFE_UNSHARE(CLONE_NEWNET); >> + >> + /* Do NOT close this FD, or both interfaces will be destroyed */ >> + childns = SAFE_OPEN("/proc/self/ns/net", O_RDONLY); >> + >> + /* Configure child namespace */ >> + CREATE_VETH_PAIR("ltp_veth1", "ltp_veth2"); >> + addr = DSTADDR; >> + NETDEV_ADD_ADDRESS_INET("ltp_veth2", htonl(addr), 26, >> + IFA_F_NOPREFIXROUTE); >> + NETDEV_SET_STATE("ltp_veth2", 1); >> + NETDEV_ADD_ROUTE_INET("ltp_veth2", 0, 0, htonl(0xfa444e40), 26, >> + 0); >> + >> + /* Configure parent namespace */ >> + NETDEV_CHANGE_NS_FD("ltp_veth1", parentns); >> + SAFE_SETNS(parentns, CLONE_NEWNET); >> + addr = SRCADDR_BASE; /* 250.68.78.65 */ >> + >> + for (i = 0; i < SRCADDR_COUNT; i++, addr++) { >> + NETDEV_ADD_ADDRESS_INET("ltp_veth1", htonl(addr), 26, >> + IFA_F_NOPREFIXROUTE); >> + } >> + >> + NETDEV_SET_STATE("ltp_veth1", 1); >> + NETDEV_ADD_ROUTE_INET("ltp_veth1", 0, 0, htonl(0xfa444e00), 26, 0); >> + SAFE_FILE_PRINTF("/proc/sys/net/ipv4/conf/ltp_veth1/forwarding", "1"); >> + >> + /* Open test sockets */ >> + for (i = 0; i < SRCADDR_COUNT; i++) { >> + ipaddr.sin_addr.s_addr = htonl(SRCADDR_BASE + i); >> + fds[i] = SAFE_SOCKET(AF_INET, SOCK_DGRAM, 0); >> + SAFE_SETSOCKOPT_INT(fds[i], IPPROTO_IP, IP_RECVERR, 1); >> + SAFE_BIND(fds[i], (struct sockaddr *)&ipaddr, sizeof(ipaddr)); >> + } >> +} >> + >> +static int count_icmp_errors(int fd) >> +{ >> + int error_count = 0; >> + ssize_t len; >> + char msgbuf[BUFSIZE], errbuf[BUFSIZE]; >> + struct sockaddr_in addr; >> + struct cmsghdr *cmsg; >> + struct sock_extended_err exterr; >> + struct iovec iov = { >> + .iov_base = msgbuf, >> + .iov_len = BUFSIZE >> + }; >> + >> + while (1) { >> + struct msghdr msg = { >> + .msg_name = (struct sockaddr *)&addr, >> + .msg_namelen = sizeof(addr), >> + .msg_iov = &iov, >> + .msg_iovlen = 1, >> + .msg_flags = 0, >> + .msg_control = errbuf, >> + .msg_controllen = BUFSIZE >> + }; >> + >> + memset(errbuf, 0, BUFSIZE); >> + errno = 0; >> + len = recvmsg(fd, &msg, MSG_ERRQUEUE); >> + >> + if (len == -1) { >> + if (errno == EWOULDBLOCK || errno == EAGAIN) >> + break; >> + >> + tst_brk(TBROK | TERRNO, "recvmsg() failed"); >> + } >> + >> + if (len < 0) { >> + tst_brk(TBROK | TERRNO, >> + "Invalid recvmsg() return value %zd", len); >> + } >> + >> + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; >> + cmsg = CMSG_NXTHDR(&msg, cmsg)) { >> + if (cmsg->cmsg_level != SOL_IP) >> + continue; >> + >> + if (cmsg->cmsg_type != IP_RECVERR) >> + continue; >> + >> + memcpy(&exterr, CMSG_DATA(cmsg), sizeof(exterr)); >> + >> + if (exterr.ee_origin != SO_EE_ORIGIN_ICMP) >> + tst_brk(TBROK, "Unexpected non-ICMP error"); >> + >> + if (exterr.ee_errno != ECONNREFUSED) { >> + TST_ERR = exterr.ee_errno; >> + tst_brk(TBROK | TTERRNO, >> + "Unexpected ICMP error"); >> + } >> + >> + error_count++; >> + } >> + } >> + >> + return error_count; >> +} >> + >> +static int packet_batch(const struct sockaddr *addr, socklen_t addrsize) >> +{ >> + int i, j, error_count = 0; >> + char data = 0; >> + >> + for (i = 0; i < SRCADDR_COUNT; i++) { >> + for (j = 0; j < 2; j++) { >> + error_count += count_icmp_errors(fds[i]); >> + TEST(sendto(fds[i], &data, sizeof(data), 0, addr, >> + addrsize)); >> + >> + if (TST_RET == -1) { >> + if (TST_ERR == ECONNREFUSED) { >> + j--; /* flush ICMP errors and retry */ >> + continue; >> + } >> + >> + tst_brk(TBROK | TTERRNO, "sento() failed"); >> + } >> + >> + if (TST_RET < 0) { >> + tst_brk(TBROK | TTERRNO, >> + "Invalid sento() return value %ld", >> + TST_RET); >> + } >> + } >> + } >> + >> + /* Wait and collect pending ICMP errors */ >> + sleep(2); > > Can we do something better than sleep() here? > > Maybe just loop over the loop that reads errors with some short sleep > and exit if we haven't got any new errors for a while? > > Something as: > > unsigned int timeout_ms = 2000; > unsigned int no_changes_ms = 0; > > while (timeout_ms) { > int last_err_cnt = error_count; > > for (i = 0; i < SRCADDR_COUNT; i++) > error_count += count_icmp_errors(fds[i]); > > if (last_err_cnt == error_count) > no_changes_ms++; > else > no_changes_ms = 0; > > > if (no_changes_ms > 500) > break; > > usleep(1000); > timeout_ms--; > } Well, that's a really tough question since we have no other way of synchronizing here and we don't even know how many errors we're supposed to get... Let's ask Michal and Nicolai for advice. >> + for (i = 0; i < SRCADDR_COUNT; i++) >> + error_count += count_icmp_errors(fds[i]); >> + >> + return error_count; >> +} >> + >> +static void run(void) >> +{ >> + int i, errors_baseline, errors; >> + struct sockaddr_in addr = { >> + .sin_family = AF_INET, >> + .sin_port = TST_GET_UNUSED_PORT(AF_INET, SOCK_DGRAM), >> + .sin_addr = { htonl(DSTADDR) } >> + }; >> + >> + errors_baseline = packet_batch((struct sockaddr *)&addr, sizeof(addr)); >> + errors = errors_baseline; >> + tst_res(TINFO, "Batch 0: Got %d ICMP errors", errors); >> + >> + for (i = 1; i < BATCH_COUNT && errors == errors_baseline; i++) { >> + errors = packet_batch((struct sockaddr *)&addr, sizeof(addr)); >> + tst_res(TINFO, "Batch %d: Got %d ICMP errors", i, errors); >> + } >> + >> + if (errors == errors_baseline) { >> + tst_res(TFAIL, >> + "ICMP rate limit not randomized, system is vulnerable"); >> + return; >> + } >> + >> + tst_res(TPASS, "ICMP rate limit is randomized"); >> +} >> + >> +static void cleanup(void) >> +{ >> + int i; >> + >> + for (i = 0; i < SRCADDR_COUNT; i++) >> + if (fds[i] >= 0) >> + SAFE_CLOSE(fds[i]); >> + >> + if (childns >= 0) >> + SAFE_CLOSE(childns); >> + >> + if (parentns >= 0) >> + SAFE_CLOSE(parentns); >> +} >> + >> +static struct tst_test test = { >> + .test_all = run, >> + .setup = setup, >> + .cleanup = cleanup, >> + .needs_kconfigs = (const char *[]) { >> + "CONFIG_USER_NS=y", >> + "CONFIG_NET_NS=y", >> + NULL >> + }, >> + .tags = (const struct tst_tag[]) { >> + {"linux-git", "b38e7819cae9"}, >> + {"CVE", "2020-25705"}, >> + {} >> + } >> +}; >> -- >> 2.31.1 >> >> >> -- >> Mailing list info: https://lists.linux.it/listinfo/ltp >
diff --git a/runtest/cve b/runtest/cve index f650854f9..eea951576 100644 --- a/runtest/cve +++ b/runtest/cve @@ -60,4 +60,5 @@ cve-2019-8912 af_alg07 cve-2020-11494 pty04 cve-2020-14386 sendto03 cve-2020-14416 pty03 +cve-2020-25705 cve-2020-25705 cve-2020-29373 io_uring02 diff --git a/testcases/cve/.gitignore b/testcases/cve/.gitignore index 01a3e4c8f..7078d1ac3 100644 --- a/testcases/cve/.gitignore +++ b/testcases/cve/.gitignore @@ -10,3 +10,4 @@ stack_clash cve-2017-17052 cve-2017-16939 cve-2017-17053 +cve-2020-25705 diff --git a/testcases/cve/cve-2020-25705.c b/testcases/cve/cve-2020-25705.c new file mode 100644 index 000000000..7d6bbafa8 --- /dev/null +++ b/testcases/cve/cve-2020-25705.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 SUSE LLC + * Author: Nicolai Stange <nstange@suse.de> + * LTP port: Martin Doucha <mdoucha@suse.cz> + * + * CVE-2020-25705 + * + * Test of ICMP rate limiting behavior that may be abused for DNS cache + * poisoning attack. Send a few batches of 100 packets to a closed UDP port + * and count the ICMP errors. If the number of errors is always the same + * for each batch (not randomized), the system is vulnerable. Send packets + * from multiple IP addresses to bypass per-address ICMP throttling. + * + * Fixed in: + * + * commit b38e7819cae946e2edf869e604af1e65a5d241c5 + * Author: Eric Dumazet <edumazet@google.com> + * Date: Thu Oct 15 11:42:00 2020 -0700 + * + * icmp: randomize the global rate limiter + */ + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <linux/if_addr.h> +#include <linux/errqueue.h> + +#include <sched.h> +#include <limits.h> +#include "tst_test.h" +#include "tst_netdevice.h" + +#define DSTADDR 0xfa444e02 /* 250.68.78.2 */ +#define SRCADDR_BASE 0xfa444e41 /* 250.68.78.65 */ +#define SRCADDR_COUNT 50 +#define BATCH_COUNT 8 +#define BUFSIZE 1024 + +static int parentns = -1, childns = -1; +static int fds[SRCADDR_COUNT]; + +static void setup(void) +{ + struct sockaddr_in ipaddr = { .sin_family = AF_INET }; + uint32_t addr; + int i; + int real_uid = getuid(); + int real_gid = getgid(); + + for (i = 0; i < SRCADDR_COUNT; i++) + fds[i] = -1; + + SAFE_UNSHARE(CLONE_NEWUSER); + SAFE_UNSHARE(CLONE_NEWNET); + SAFE_FILE_PRINTF("/proc/self/setgroups", "deny"); + SAFE_FILE_PRINTF("/proc/self/uid_map", "0 %d 1\n", real_uid); + SAFE_FILE_PRINTF("/proc/self/gid_map", "0 %d 1\n", real_gid); + + /* + * Create network namespace to hide the destination interface from + * the test process. + */ + parentns = SAFE_OPEN("/proc/self/ns/net", O_RDONLY); + SAFE_UNSHARE(CLONE_NEWNET); + + /* Do NOT close this FD, or both interfaces will be destroyed */ + childns = SAFE_OPEN("/proc/self/ns/net", O_RDONLY); + + /* Configure child namespace */ + CREATE_VETH_PAIR("ltp_veth1", "ltp_veth2"); + addr = DSTADDR; + NETDEV_ADD_ADDRESS_INET("ltp_veth2", htonl(addr), 26, + IFA_F_NOPREFIXROUTE); + NETDEV_SET_STATE("ltp_veth2", 1); + NETDEV_ADD_ROUTE_INET("ltp_veth2", 0, 0, htonl(0xfa444e40), 26, + 0); + + /* Configure parent namespace */ + NETDEV_CHANGE_NS_FD("ltp_veth1", parentns); + SAFE_SETNS(parentns, CLONE_NEWNET); + addr = SRCADDR_BASE; /* 250.68.78.65 */ + + for (i = 0; i < SRCADDR_COUNT; i++, addr++) { + NETDEV_ADD_ADDRESS_INET("ltp_veth1", htonl(addr), 26, + IFA_F_NOPREFIXROUTE); + } + + NETDEV_SET_STATE("ltp_veth1", 1); + NETDEV_ADD_ROUTE_INET("ltp_veth1", 0, 0, htonl(0xfa444e00), 26, 0); + SAFE_FILE_PRINTF("/proc/sys/net/ipv4/conf/ltp_veth1/forwarding", "1"); + + /* Open test sockets */ + for (i = 0; i < SRCADDR_COUNT; i++) { + ipaddr.sin_addr.s_addr = htonl(SRCADDR_BASE + i); + fds[i] = SAFE_SOCKET(AF_INET, SOCK_DGRAM, 0); + SAFE_SETSOCKOPT_INT(fds[i], IPPROTO_IP, IP_RECVERR, 1); + SAFE_BIND(fds[i], (struct sockaddr *)&ipaddr, sizeof(ipaddr)); + } +} + +static int count_icmp_errors(int fd) +{ + int error_count = 0; + ssize_t len; + char msgbuf[BUFSIZE], errbuf[BUFSIZE]; + struct sockaddr_in addr; + struct cmsghdr *cmsg; + struct sock_extended_err exterr; + struct iovec iov = { + .iov_base = msgbuf, + .iov_len = BUFSIZE + }; + + while (1) { + struct msghdr msg = { + .msg_name = (struct sockaddr *)&addr, + .msg_namelen = sizeof(addr), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_flags = 0, + .msg_control = errbuf, + .msg_controllen = BUFSIZE + }; + + memset(errbuf, 0, BUFSIZE); + errno = 0; + len = recvmsg(fd, &msg, MSG_ERRQUEUE); + + if (len == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) + break; + + tst_brk(TBROK | TERRNO, "recvmsg() failed"); + } + + if (len < 0) { + tst_brk(TBROK | TERRNO, + "Invalid recvmsg() return value %zd", len); + } + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != SOL_IP) + continue; + + if (cmsg->cmsg_type != IP_RECVERR) + continue; + + memcpy(&exterr, CMSG_DATA(cmsg), sizeof(exterr)); + + if (exterr.ee_origin != SO_EE_ORIGIN_ICMP) + tst_brk(TBROK, "Unexpected non-ICMP error"); + + if (exterr.ee_errno != ECONNREFUSED) { + TST_ERR = exterr.ee_errno; + tst_brk(TBROK | TTERRNO, + "Unexpected ICMP error"); + } + + error_count++; + } + } + + return error_count; +} + +static int packet_batch(const struct sockaddr *addr, socklen_t addrsize) +{ + int i, j, error_count = 0; + char data = 0; + + for (i = 0; i < SRCADDR_COUNT; i++) { + for (j = 0; j < 2; j++) { + error_count += count_icmp_errors(fds[i]); + TEST(sendto(fds[i], &data, sizeof(data), 0, addr, + addrsize)); + + if (TST_RET == -1) { + if (TST_ERR == ECONNREFUSED) { + j--; /* flush ICMP errors and retry */ + continue; + } + + tst_brk(TBROK | TTERRNO, "sento() failed"); + } + + if (TST_RET < 0) { + tst_brk(TBROK | TTERRNO, + "Invalid sento() return value %ld", + TST_RET); + } + } + } + + /* Wait and collect pending ICMP errors */ + sleep(2); + + for (i = 0; i < SRCADDR_COUNT; i++) + error_count += count_icmp_errors(fds[i]); + + return error_count; +} + +static void run(void) +{ + int i, errors_baseline, errors; + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = TST_GET_UNUSED_PORT(AF_INET, SOCK_DGRAM), + .sin_addr = { htonl(DSTADDR) } + }; + + errors_baseline = packet_batch((struct sockaddr *)&addr, sizeof(addr)); + errors = errors_baseline; + tst_res(TINFO, "Batch 0: Got %d ICMP errors", errors); + + for (i = 1; i < BATCH_COUNT && errors == errors_baseline; i++) { + errors = packet_batch((struct sockaddr *)&addr, sizeof(addr)); + tst_res(TINFO, "Batch %d: Got %d ICMP errors", i, errors); + } + + if (errors == errors_baseline) { + tst_res(TFAIL, + "ICMP rate limit not randomized, system is vulnerable"); + return; + } + + tst_res(TPASS, "ICMP rate limit is randomized"); +} + +static void cleanup(void) +{ + int i; + + for (i = 0; i < SRCADDR_COUNT; i++) + if (fds[i] >= 0) + SAFE_CLOSE(fds[i]); + + if (childns >= 0) + SAFE_CLOSE(childns); + + if (parentns >= 0) + SAFE_CLOSE(parentns); +} + +static struct tst_test test = { + .test_all = run, + .setup = setup, + .cleanup = cleanup, + .needs_kconfigs = (const char *[]) { + "CONFIG_USER_NS=y", + "CONFIG_NET_NS=y", + NULL + }, + .tags = (const struct tst_tag[]) { + {"linux-git", "b38e7819cae9"}, + {"CVE", "2020-25705"}, + {} + } +};
Fixes #742 Signed-off-by: Martin Doucha <mdoucha@suse.cz> --- Changes since v1: New patch Changes since v2: Added missing gitignore and runfile entry for the new test runtest/cve | 1 + testcases/cve/.gitignore | 1 + testcases/cve/cve-2020-25705.c | 262 +++++++++++++++++++++++++++++++++ 3 files changed, 264 insertions(+) create mode 100644 testcases/cve/cve-2020-25705.c