From 2d3fda5fa1dc99aa8788e5f8d8bb71e682101063 Mon Sep 17 00:00:00 2001 From: Kristof Provost Date: Sat, 19 Dec 2020 16:06:03 +0100 Subject: [PATCH] pf tests: Verify (tcp) checksum modification on unaligned options It turns out pf incorrectly updates the TCP checksum if the TCP option we're modifying is not 2-byte algined with respect to the start of the packet. Create a TCP packet with such an option and throw it through a scrub rule, which will update timestamps and modify the packet. PR: 240416 MFC after: 1 week Differential revision: https://reviews.freebsd.org/D27688 --- tests/sys/netpfil/common/pft_ping.py | 69 ++++++++++++++++++++-- tests/sys/netpfil/pf/Makefile | 1 + tests/sys/netpfil/pf/checksum.sh | 85 ++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 tests/sys/netpfil/pf/checksum.sh diff --git a/tests/sys/netpfil/common/pft_ping.py b/tests/sys/netpfil/common/pft_ping.py index 5d347adf8796..d960426e4b42 100644 --- a/tests/sys/netpfil/common/pft_ping.py +++ b/tests/sys/netpfil/common/pft_ping.py @@ -28,6 +28,7 @@ import argparse import scapy.all as sp +import socket import sys from sniffer import Sniffer @@ -113,6 +114,53 @@ def ping6(send_if, dst_ip, args): req = ether / ip6 / icmp sp.sendp(req, iface=send_if, verbose=False) +def check_tcpsyn(args, packet): + dst_ip = args.to[0] + + ip = packet.getlayer(sp.IP) + if not ip: + return False + if ip.dst != dst_ip: + return False + + tcp = packet.getlayer(sp.TCP) + if not tcp: + return False + + # Verify IP checksum + chksum = ip.chksum + ip.chksum = None + new_chksum = sp.IP(sp.raw(ip)).chksum + if chksum != new_chksum: + print("Expected IP checksum %x but found %x\n" % (new_cshkum, chksum)) + return False + + # Verify TCP checksum + chksum = tcp.chksum + packet_raw = sp.raw(packet) + tcp.chksum = None + newpacket = sp.Ether(sp.raw(packet[sp.Ether])) + new_chksum = newpacket[sp.TCP].chksum + if chksum != new_chksum: + print("Expected TCP checksum %x but found %x\n" % (new_chksum, chksum)) + return False + + return True + +def tcpsyn(send_if, dst_ip, args): + opts=[('Timestamp', (1, 1)), ('MSS', 1280)] + + if args.tcpopt_unaligned: + opts = [('NOP', 0 )] + opts + + ether = sp.Ether() + ip = sp.IP(dst=dst_ip) + tcp = sp.TCP(dport=666, flags='S', options=opts) + + req = ether / ip / tcp + sp.sendp(req, iface=send_if, verbose=False) + + def main(): parser = argparse.ArgumentParser("pft_ping.py", description="Ping test tool") @@ -127,6 +175,12 @@ def main(): required=True, help='The destination IP address for the ICMP echo request') + # TCP options + parser.add_argument('--tcpsyn', action='store_true', + help='Send a TCP SYN packet') + parser.add_argument('--tcpopt_unaligned', action='store_true', + help='Include unaligned TCP options') + # Packet settings parser.add_argument('--send-tos', nargs=1, help='Set the ToS value for the transmitted packet') @@ -142,12 +196,19 @@ def main(): sniffer = None if not args.recvif is None: - sniffer = Sniffer(args, check_ping_request) + checkfn=check_ping_request + if args.tcpsyn: + checkfn=check_tcpsyn - if args.ip6: - ping6(args.sendif[0], args.to[0], args) + sniffer = Sniffer(args, checkfn) + + if args.tcpsyn: + tcpsyn(args.sendif[0], args.to[0], args) else: - ping(args.sendif[0], args.to[0], args) + if args.ip6: + ping6(args.sendif[0], args.to[0], args) + else: + ping(args.sendif[0], args.to[0], args) if sniffer: sniffer.join() diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile index 73c85320c013..68f54c801297 100644 --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -6,6 +6,7 @@ TESTSDIR= ${TESTSBASE}/sys/netpfil/pf TESTS_SUBDIRS+= ioctl ATF_TESTS_SH+= anchor \ + checksum \ forward \ fragmentation \ icmp \ diff --git a/tests/sys/netpfil/pf/checksum.sh b/tests/sys/netpfil/pf/checksum.sh new file mode 100644 index 000000000000..778ae1cd6e9f --- /dev/null +++ b/tests/sys/netpfil/pf/checksum.sh @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: BSD-2-Clause-FreeBSD +# +# Copyright (c) 2020 Kristof Provost +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +. $(atf_get_srcdir)/utils.subr + +common_dir=$(atf_get_srcdir)/../common + +atf_test_case "unaligned" "cleanup" +unaligned_head() +{ + atf_set descr 'Test unaligned checksum updates' + atf_set require.user root +} + +unaligned_body() +{ + pft_init + + epair_in=$(vnet_mkepair) + epair_out=$(vnet_mkepair) + + vnet_mkjail alcatraz ${epair_in}b ${epair_out}a + + ifconfig ${epair_in}a 192.0.2.2/24 up + route add -net 198.51.100.0/24 192.0.2.1 + + jexec alcatraz ifconfig ${epair_in}b 192.0.2.1/24 up + jexec alcatraz sysctl net.inet.ip.forwarding=1 + + jexec alcatraz ifconfig ${epair_out}a 198.51.100.1/24 up + jexec alcatraz arp -s 198.51.100.2 00:01:02:03:04:05 + + ifconfig ${epair_out}b up + + jexec alcatraz pfctl -e + pft_set_rules alcatraz \ + "scrub on ${epair_in}b reassemble tcp max-mss 1200" + + # Check aligned + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_in}a \ + --to 198.51.100.2 \ + --recvif ${epair_out}b \ + --tcpsyn + + # And unaligned + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_in}a \ + --to 198.51.100.2 \ + --recvif ${epair_out}b \ + --tcpsyn \ + --tcpopt_unaligned +} + +unaligned_cleanup() +{ + pft_cleanup +} + +atf_init_test_cases() +{ + atf_add_test_case "unaligned" +}