[LTP] [PATCH v4] Add test for CVE 2020-25705
Martin Doucha
mdoucha@suse.cz
Thu May 27 13:34:48 CEST 2021
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
Changes since v3:
- rename test from cve-2020-25705 to icmp_rate_limit01
- add #include <time.h> to fix compilation on older systems
- use docparser tag for test description
- change some magic numbers to named constants
- comment cleanup
I've tried several alternatives to the sleep() call in packet_batch() but none
of them work. Trying to receive errors too early will cause kernel to silently
discard some of them and the test will either randomly pass on a vulnerable
system or fail on a fixed one. 2-second sleep() is the minimum for reliable
results.
runtest/cve | 1 +
testcases/cve/.gitignore | 1 +
testcases/cve/icmp_rate_limit01.c | 272 ++++++++++++++++++++++++++++++
3 files changed, 274 insertions(+)
create mode 100644 testcases/cve/icmp_rate_limit01.c
diff --git a/runtest/cve b/runtest/cve
index 3beb88bb0..9da58d524 100644
--- a/runtest/cve
+++ b/runtest/cve
@@ -60,5 +60,6 @@ cve-2019-8912 af_alg07
cve-2020-11494 pty04
cve-2020-14386 sendto03
cve-2020-14416 pty03
+cve-2020-25705 icmp_rate_limit01
cve-2020-29373 io_uring02
cve-2021-3444 bpf_prog05
diff --git a/testcases/cve/.gitignore b/testcases/cve/.gitignore
index 01a3e4c8f..eb0a8b37d 100644
--- a/testcases/cve/.gitignore
+++ b/testcases/cve/.gitignore
@@ -10,3 +10,4 @@ stack_clash
cve-2017-17052
cve-2017-16939
cve-2017-17053
+icmp_rate_limit01
diff --git a/testcases/cve/icmp_rate_limit01.c b/testcases/cve/icmp_rate_limit01.c
new file mode 100644
index 000000000..0c2ac44df
--- /dev/null
+++ b/testcases/cve/icmp_rate_limit01.c
@@ -0,0 +1,272 @@
+// 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 <time.h>
+#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 DSTNET 0xfa444e00 /* 250.68.78.0 */
+#define SRCNET 0xfa444e40 /* 250.68.78.64 */
+#define DSTADDR 0xfa444e02 /* 250.68.78.2 */
+#define SRCADDR_BASE 0xfa444e41 /* 250.68.78.65 */
+#define SRCADDR_COUNT 50
+#define NETMASK 26
+#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");
+ NETDEV_ADD_ADDRESS_INET("ltp_veth2", htonl(DSTADDR), NETMASK,
+ IFA_F_NOPREFIXROUTE);
+ NETDEV_SET_STATE("ltp_veth2", 1);
+ NETDEV_ADD_ROUTE_INET("ltp_veth2", 0, 0, htonl(SRCNET), NETMASK, 0);
+
+ /* Configure parent namespace */
+ NETDEV_CHANGE_NS_FD("ltp_veth1", parentns);
+ SAFE_SETNS(parentns, CLONE_NEWNET);
+ addr = SRCADDR_BASE;
+
+ for (i = 0; i < SRCADDR_COUNT; i++, addr++) {
+ NETDEV_ADD_ADDRESS_INET("ltp_veth1", htonl(addr), NETMASK,
+ IFA_F_NOPREFIXROUTE);
+ }
+
+ NETDEV_SET_STATE("ltp_veth1", 1);
+ NETDEV_ADD_ROUTE_INET("ltp_veth1", 0, 0, htonl(DSTNET), NETMASK, 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. Waiting less than 2 seconds
+ * will make the test unreliable. Looping over each socket multiple
+ * times (with or without poll()) will cause kernel to silently
+ * discard ICMP errors, allowing the test to pass on vulnerable
+ * systems.
+ */
+ 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"},
+ {}
+ }
+};
--
2.31.1
More information about the ltp
mailing list