273 lines
6.4 KiB
C
273 lines
6.4 KiB
C
// 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/errqueue.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include "lapi/if_addr.h"
|
|
#include "tst_test.h"
|
|
#include "tst_netdevice.h"
|
|
#include "lapi/sched.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;
|
|
|
|
for (i = 0; i < SRCADDR_COUNT; i++)
|
|
fds[i] = -1;
|
|
|
|
tst_setup_netns();
|
|
|
|
/*
|
|
* 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_VETH",
|
|
"CONFIG_USER_NS=y",
|
|
"CONFIG_NET_NS=y",
|
|
NULL
|
|
},
|
|
.save_restore = (const struct tst_path_val[]) {
|
|
{"/proc/sys/user/max_user_namespaces", "1024", TST_SR_SKIP},
|
|
{}
|
|
},
|
|
.tags = (const struct tst_tag[]) {
|
|
{"linux-git", "b38e7819cae9"},
|
|
{"CVE", "2020-25705"},
|
|
{}
|
|
}
|
|
};
|