[LTP] [PATCH v3 6/6] Add test for CVE 2020-25705
Petr Vorel
pvorel@suse.cz
Wed May 5 12:04:21 CEST 2021
> 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
More information about the ltp
mailing list