Add a selftest to test that ARP bcast/null poisioning checks are never bypassed. Signed-off-by: Marc Suñé --- tools/testing/selftests/net/.gitignore | 1 + tools/testing/selftests/net/Makefile | 2 + .../selftests/net/arp_no_bcastnull_poision.sh | 159 ++++++++++++++++++ tools/testing/selftests/net/arp_send.c | 138 +++++++++++++++ 4 files changed, 300 insertions(+) create mode 100755 tools/testing/selftests/net/arp_no_bcastnull_poision.sh create mode 100644 tools/testing/selftests/net/arp_send.c diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore index 6930fe926c58..fd08ceeab07c 100644 --- a/tools/testing/selftests/net/.gitignore +++ b/tools/testing/selftests/net/.gitignore @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +arp_send bind_bhash bind_timewait bind_wildcard diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index b66ba04f19d9..8308f0067547 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -9,6 +9,7 @@ CFLAGS += -I../ TEST_PROGS := \ altnames.sh \ amt.sh \ + arp_no_bcastnull_poision.sh \ arp_ndisc_evict_nocarrier.sh \ arp_ndisc_untracked_subnets.sh \ bareudp.sh \ @@ -163,6 +164,7 @@ TEST_GEN_FILES := \ # end of TEST_GEN_FILES TEST_GEN_PROGS := \ + arp_send \ bind_timewait \ bind_wildcard \ epoll_busy_poll \ diff --git a/tools/testing/selftests/net/arp_no_bcastnull_poision.sh b/tools/testing/selftests/net/arp_no_bcastnull_poision.sh new file mode 100755 index 000000000000..d0b9241599f1 --- /dev/null +++ b/tools/testing/selftests/net/arp_no_bcastnull_poision.sh @@ -0,0 +1,159 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Tests that ARP announcements with Broadcast or NULL mac are never +# accepted +# + +source lib.sh + +readonly V4_ADDR0="10.0.10.1" +readonly V4_ADDR1="10.0.10.2" +readonly BCAST_MAC="ff:ff:ff:ff:ff:ff" +readonly NULL_MAC="00:00:00:00:00:00" +readonly VALID_MAC="02:01:02:03:04:05" +readonly ARP_REQ=1 +readonly ARP_REPLY=2 +nsid=100 +ret=0 +veth0_ifindex=0 +veth1_mac= + +setup() { + setup_ns PEER_NS + + ip link add name veth0 type veth peer name veth1 + ip link set dev veth0 up + ip link set dev veth1 netns ${PEER_NS} + ip netns exec ${PEER_NS} ip link set dev veth1 up + ip addr add ${V4_ADDR0}/24 dev veth0 + ip netns exec ${PEER_NS} ip addr add ${V4_ADDR1}/24 dev veth1 + ip netns exec ${PEER_NS} ip route add default via ${V4_ADDR0} dev veth1 + + # Raise ARP timers to avoid flakes due to refreshes + sysctl -w net.ipv4.neigh.veth0.base_reachable_time=3600 \ + >/dev/null 2>&1 + ip netns exec ${PEER_NS} \ + sysctl -w net.ipv4.neigh.veth1.gc_stale_time=3600 \ + >/dev/null 2>&1 + ip netns exec ${PEER_NS} \ + sysctl -w net.ipv4.neigh.veth1.base_reachable_time=3600 \ + >/dev/null 2>&1 + + veth0_ifindex=$(ip -j link show veth0 | jq -r '.[0].ifindex') + veth1_mac="$(ip netns exec ${PEER_NS} ip -j link show veth1 | \ + jq -r '.[0].address' )" +} + +cleanup() { + ip neigh flush dev veth0 + ip link del veth0 + cleanup_ns ${PEER_NS} +} + +# Make sure ARP announcement with invalid MAC is never learnt +run_no_arp_poisoning() { + local l2_dmac=${1} + local tmac=${2} + local op=${3} + + ret=0 + + ip netns exec ${PEER_NS} ip neigh flush dev veth1 >/dev/null 2>&1 + ip netns exec ${PEER_NS} ping -c 1 ${V4_ADDR0} >/dev/null 2>&1 + + # Poison with a valid MAC to ensure injection is working + ./arp_send ${veth0_ifindex} ${BCAST_MAC} ${VALID_MAC} ${op} \ + ${V4_ADDR0} ${VALID_MAC} ${V4_ADDR0} ${VALID_MAC} + + neigh=$(ip netns exec ${PEER_NS} ip neigh show ${V4_ADDR0} | \ + grep ${VALID_MAC}) + if [ "${neigh}" == "" ]; then + echo "ERROR: unable to ARP poision with a valid MAC ${VALID_MAC}" + ip netns exec ${PEER_NS} ip neigh show ${V4_ADDR0} + ret=1 + return + fi + + # Poison with tmac + ./arp_send ${veth0_ifindex} ${l2_dmac} ${VALID_MAC} ${op} \ + ${V4_ADDR0} ${tmac} ${V4_ADDR0} ${tmac} + + neigh=$(ip netns exec ${PEER_NS} ip neigh show ${V4_ADDR0} | \ + grep ${tmac}) + if [ "${neigh}" != "" ]; then + echo "ERROR: ARP entry learnt for ${tmac} announcement." + ip netns exec ${PEER_NS} ip neigh show ${V4_ADDR0} + ret=1 + return + fi +} + +print_test_result() { + local msg=${1} + local rc=${2} + + if [ ${rc} == 0 ]; then + printf "TEST: %-60s [ OK ]" "${msg}" + else + printf "TEST: %-60s [ FAIL ]" "${msg}" + fi +} + +run_all_tests() { + local results + + setup + + ## ARP + # Broadcast gARPs + msg="1.1 ARP no poisoning dmac=bcast reply sha=bcast" + run_no_arp_poisoning ${BCAST_MAC} ${BCAST_MAC} ${ARP_REPLY} + results+="$(print_test_result "${msg}" ${ret})\n" + + msg="1.2 ARP no poisoning dmac=bcast reply sha=null" + run_no_arp_poisoning ${BCAST_MAC} ${NULL_MAC} ${ARP_REPLY} + results+="$(print_test_result "${msg}" ${ret})\n" + + msg="1.3 ARP no poisoning dmac=bcast req sha=bcast" + run_no_arp_poisoning ${BCAST_MAC} ${BCAST_MAC} ${ARP_REQ} + results+="$(print_test_result "${msg}" ${ret})\n" + + msg="1.4 ARP no poisoning dmac=bcast req sha=null" + run_no_arp_poisoning ${BCAST_MAC} ${NULL_MAC} ${ARP_REQ} + results+="$(print_test_result "${msg}" ${ret})\n" + + # Targeted gARPs + msg="1.5 ARP no poisoning dmac=veth0 reply sha=bcast" + run_no_arp_poisoning ${veth1_mac} ${BCAST_MAC} ${ARP_REPLY} + results+="$(print_test_result "${msg}" ${ret})\n" + + msg="1.6 ARP no poisoning dmac=veth0 reply sha=null" + run_no_arp_poisoning ${veth1_mac} ${NULL_MAC} ${ARP_REPLY} + results+="$(print_test_result "${msg}" ${ret})\n" + + msg="1.7 ARP no poisoning dmac=veth0 req sha=bcast" + run_no_arp_poisoning ${veth1_mac} ${BCAST_MAC} ${ARP_REQ} + results+="$(print_test_result "${msg}" ${ret})\n" + + msg="1.8 ARP no poisoning dmac=veth0 req sha=null" + run_no_arp_poisoning ${veth1_mac} ${NULL_MAC} ${ARP_REQ} + results+="$(print_test_result "${msg}" ${ret})\n" + + cleanup + + printf '%b' "${results}" +} + +if [ "$(id -u)" -ne 0 ];then + echo "SKIP: Need root privileges" + exit $ksft_skip; +fi + +if [ ! -x "$(command -v ip)" ]; then + echo "SKIP: Could not run test without ip tool" + exit $ksft_skip +fi + +run_all_tests +exit $ret diff --git a/tools/testing/selftests/net/arp_send.c b/tools/testing/selftests/net/arp_send.c new file mode 100644 index 000000000000..463ee435c9c1 --- /dev/null +++ b/tools/testing/selftests/net/arp_send.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +struct arp_pkt { + struct ethhdr eth; + struct { + struct arphdr hdr; + + /* Variable part for Ethernet IP ARP */ + unsigned char ar_sha[ETH_ALEN]; /* sender hardware address */ + __be32 ar_sip; /* sender IP address */ + unsigned char ar_tha[ETH_ALEN]; /* target hardware address */ + __be32 ar_tip; /* target IP address */ + } __packed arp; +} __packed; + +int parse_opts(int argc, char **argv, int *ifindex, struct arp_pkt *pkt) +{ + int rc; + struct ether_addr *mac; + uint16_t op_code; + + if (argc != 9) { + fprintf(stderr, "Usage: %s \n", + argv[0]); + return -1; + } + + *ifindex = atoi(argv[1]); + mac = ether_aton(argv[2]); + if (!mac) { + fprintf(stderr, "Unable to parse mac_dst from '%s'\n", argv[2]); + return -1; + } + + /* Ethernet */ + memcpy(pkt->eth.h_dest, mac, ETH_ALEN); + mac = ether_aton(argv[3]); + if (!mac) { + fprintf(stderr, "Unable to parse mac_src from '%s'\n", argv[3]); + return -1; + } + memcpy(pkt->eth.h_source, mac, ETH_ALEN); + pkt->eth.h_proto = htons(ETH_P_ARP); + + /* ARP */ + op_code = atol(argv[4]); + if (op_code != ARPOP_REQUEST && op_code != ARPOP_REPLY) { + fprintf(stderr, "Invalid ARP op %s\n", argv[4]); + return -1; + } + pkt->arp.hdr.ar_op = htons(op_code); + + pkt->arp.hdr.ar_hrd = htons(0x1); /* Ethernet */ + pkt->arp.hdr.ar_pro = htons(ETH_P_IP); + pkt->arp.hdr.ar_hln = ETH_ALEN; + pkt->arp.hdr.ar_pln = 4; + + rc = inet_pton(AF_INET, argv[5], &pkt->arp.ar_tip); + if (rc != 1) { + fprintf(stderr, "Invalid IPv4 address %s\n", argv[5]); + return -1; + } + rc = inet_pton(AF_INET, argv[7], &pkt->arp.ar_sip); + if (rc != 1) { + fprintf(stderr, "Invalid IPv4 address %s\n", argv[7]); + return -1; + } + + mac = ether_aton(argv[6]); + if (!mac) { + fprintf(stderr, "Unable to parse target-hwaddr from '%s'\n", + argv[6]); + return -1; + } + memcpy(pkt->arp.ar_tha, mac, ETH_ALEN); + mac = ether_aton(argv[8]); + if (!mac) { + fprintf(stderr, "Unable to parse sender-hwaddr from '%s'\n", + argv[8]); + return -1; + } + memcpy(pkt->arp.ar_sha, mac, ETH_ALEN); + + return 0; +} + +int main(int argc, char **argv) +{ + int rc, fd; + struct sockaddr_ll bind_addr = {0}; + int ifindex; + struct arp_pkt pkt = {0}; + + if (parse_opts(argc, argv, &ifindex, &pkt) < 0) + return -1; + + fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (fd < 0) { + fprintf(stderr, "Unable to open raw socket(%d). Need root privileges?\n", + fd); + return 1; + } + + bind_addr.sll_family = AF_PACKET; + bind_addr.sll_protocol = htons(ETH_P_ALL); + bind_addr.sll_ifindex = ifindex; + + rc = bind(fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)); + if (rc < 0) { + fprintf(stderr, "Unable to bind raw socket(%d). Invalid iface '%d'?\n", + rc, ifindex); + return 1; + } + + rc = send(fd, &pkt, sizeof(pkt), 0); + if (rc < 0) { + fprintf(stderr, "Unable to send packet: %d\n", rc); + return 1; + } + + return 0; +} -- 2.47.3