From 8f79da2a7ab2d3087d9588c271f22381fe816d25 Mon Sep 17 00:00:00 2001 From: bluhm Date: Tue, 12 Mar 2024 21:31:29 +0000 Subject: [PATCH] Add regress test showing that OpenBSD IPv6 fragment reassembly is not affected by FreeBSD-SA-23:06.ipv6 security advisory. Scapy test frag6_oversize.py reassembles fragments of a packet too big to fit. Test frag6_unfragsize.py also plays games with ECN bits and hop-by-hop extension header to check overflow protection. ICMP6 parameter problem responses are expected. As pf does not generate such ICMP6 error packets, these tests are only run with frag6_input() in the IPv6 stack. --- regress/sys/netinet6/frag6/LICENSE | 2 +- regress/sys/netinet6/frag6/Makefile | 8 +- regress/sys/netinet6/frag6/frag6_oversize.py | 65 ++++++++++++++++ .../sys/netinet6/frag6/frag6_unfragsize.py | 77 +++++++++++++++++++ 4 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 regress/sys/netinet6/frag6/frag6_oversize.py create mode 100644 regress/sys/netinet6/frag6/frag6_unfragsize.py diff --git a/regress/sys/netinet6/frag6/LICENSE b/regress/sys/netinet6/frag6/LICENSE index 866739d7ee3..95f9e99b44c 100644 --- a/regress/sys/netinet6/frag6/LICENSE +++ b/regress/sys/netinet6/frag6/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2020 Alexander Bluhm +Copyright (c) 2012-2024 Alexander Bluhm Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/regress/sys/netinet6/frag6/Makefile b/regress/sys/netinet6/frag6/Makefile index 8d1436c4f65..cec85216c42 100644 --- a/regress/sys/netinet6/frag6/Makefile +++ b/regress/sys/netinet6/frag6/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.29 2023/09/08 21:15:02 bluhm Exp $ +# $OpenBSD: Makefile,v 1.30 2024/03/12 21:31:29 bluhm Exp $ # The following ports must be installed: # @@ -88,7 +88,11 @@ stamp-pf: addr.py pf.conf FRAG6_SCRIPTS !!= cd ${.CURDIR} && ls -1 frag6*.py run-stack-frag6_queuelimit.py: - # the stack does not limit the amount of fragments during reassembly + # stack does not limit the amount of fragments during reassembly + @echo DISABLED + +run-pf-frag6_oversize.py run-pf-frag6_unfragsize.py: + # pf does not send icmp parameter problem, so test does not work @echo DISABLED run-stack-frag6_doubleatomic.py: addr.py stamp-stack diff --git a/regress/sys/netinet6/frag6/frag6_oversize.py b/regress/sys/netinet6/frag6/frag6_oversize.py new file mode 100644 index 00000000000..3fbdfcae53c --- /dev/null +++ b/regress/sys/netinet6/frag6/frag6_oversize.py @@ -0,0 +1,65 @@ +#!/usr/local/bin/python3 + +print("ping6 fragments in total larger than IP maximum packet") + +# |---------| +# |--------| +# ... ... +# |--------| + +import os +from addr import * +from scapy.all import * + +pid=os.getpid() +eid=pid & 0xffff +payload=b"ABCDEFGHIJKLMNOP" +packet=IPv6(src=LOCAL_ADDR6, dst=REMOTE_ADDR6)/ \ + ICMPv6EchoRequest(id=eid, data=4095*payload) +plen=IPv6(raw(packet)).plen +print("plen=%u" % (plen)) +if plen != 0xfff8: + print("PLEN!=%u" % (0xfff8)) + exit(2) +bytes=bytes(packet)+b"12345678" + +frag=[] +fid=pid & 0xffffffff +frag.append(IPv6ExtHdrFragment(nh=58, id=fid, m=1)/bytes[40:40+2**10]) +off=2**7 +while off < 2**13: + frag.append(IPv6ExtHdrFragment(nh=58, id=fid, offset=off)/ \ + bytes[40+off*8:40+off*8+2**10]) + off+=2**7 +eth=[] +for f in frag: + pkt=IPv6(src=LOCAL_ADDR6, dst=REMOTE_ADDR6)/f + eth.append(Ether(src=LOCAL_MAC, dst=REMOTE_MAC)/pkt) + +if os.fork() == 0: + time.sleep(1) + sendp(eth, iface=LOCAL_IF) + os._exit(0) + +ans=sniff(iface=LOCAL_IF, timeout=3, filter= + "ip6 and src "+REMOTE_ADDR6+" and dst "+LOCAL_ADDR6+" and icmp6") +for a in ans: + print("type %d" % (a.payload.payload.type)) + print("icmp %s" % (icmp6types[a.payload.payload.type])) + if a and a.type == ETH_P_IPV6 and \ + ipv6nh[a.payload.nh] == 'ICMPv6' and \ + icmp6types[a.payload.payload.type] == 'Parameter problem': + print("code=%u" % (a.payload.payload.code)) + # 0: 'erroneous header field encountered' + if a.payload.payload.code != 0: + print("WRONG PARAMETER PROBLEM CODE") + exit(1) + ptr=a.payload.payload.ptr + print("ptr=%u" % (ptr)) + # 42: sizeof IPv6 header + offset in fragment header + if ptr != 42: + print("PTR!=%u" % (ptr)) + exit(1) + exit(0) +print("NO ICMP PARAMETER PROBLEM") +exit(2) diff --git a/regress/sys/netinet6/frag6/frag6_unfragsize.py b/regress/sys/netinet6/frag6/frag6_unfragsize.py new file mode 100644 index 00000000000..05585d7d95e --- /dev/null +++ b/regress/sys/netinet6/frag6/frag6_unfragsize.py @@ -0,0 +1,77 @@ +#!/usr/local/bin/python3 + +print("ping6 fragments with options total larger than IP maximum packet") + +# |--------| +# ... ... +# |--------| +# drop first fragment with ECN conflict, reinsert with longer unfrag part +# |---------| +# HopByHop|---------| + +import os +from addr import * +from scapy.all import * + +pid=os.getpid() +eid=pid & 0xffff +payload=b"ABCDEFGHIJKLMNOP" +packet=IPv6(src=LOCAL_ADDR6, dst=REMOTE_ADDR6)/ \ + ICMPv6EchoRequest(id=eid, data=4095*payload) +plen=IPv6(raw(packet)).plen +print("plen=%u" % (plen)) +if plen != 0xfff8: + print("PLEN!=%u" % (0xfff8)) + exit(2) +bytes=bytes(packet) + +frag=[] +fid=pid & 0xffffffff +off=2**7 +while off < 2**13: + frag.append(IPv6ExtHdrFragment(nh=58, id=fid, offset=off)/ \ + bytes[40+off*8:40+off*8+2**10]) + off+=2**7 +eth=[] +for f in frag: + pkt=IPv6(src=LOCAL_ADDR6, dst=REMOTE_ADDR6)/f + eth.append(Ether(src=LOCAL_MAC, dst=REMOTE_MAC)/pkt) + +# first fragment with ECN to be dropped +eth.append(Ether(src=LOCAL_MAC, dst=REMOTE_MAC)/ + IPv6(src=LOCAL_ADDR6, dst=REMOTE_ADDR6, tc=3)/ + IPv6ExtHdrFragment(nh=58, id=fid, m=1)/bytes[40:40+2**10]) + +# resend first fragment with unfragmentable part too long for IP plen +eth.append(Ether(src=LOCAL_MAC, dst=REMOTE_MAC)/ + IPv6(src=LOCAL_ADDR6, dst=REMOTE_ADDR6)/ + IPv6ExtHdrHopByHop(options=PadN(optdata="\0"*4))/ + IPv6ExtHdrFragment(nh=58, id=fid, m=1)/bytes[40:40+2**10]) + +if os.fork() == 0: + time.sleep(1) + sendp(eth, iface=LOCAL_IF) + os._exit(0) + +ans=sniff(iface=LOCAL_IF, timeout=3, filter= + "ip6 and src "+REMOTE_ADDR6+" and dst "+LOCAL_ADDR6+" and icmp6") +for a in ans: + print("type %d" % (a.payload.payload.type)) + print("icmp %s" % (icmp6types[a.payload.payload.type])) + if a and a.type == ETH_P_IPV6 and \ + ipv6nh[a.payload.nh] == 'ICMPv6' and \ + icmp6types[a.payload.payload.type] == 'Parameter problem': + print("code=%u" % (a.payload.payload.code)) + # 0: 'erroneous header field encountered' + if a.payload.payload.code != 0: + print("WRONG PARAMETER PROBLEM CODE") + exit(1) + ptr=a.payload.payload.ptr + print("ptr=%u" % (ptr)) + # 42: sizeof IPv6 header + offset in fragment header + if ptr != 42: + print("PTR!=%u" % (ptr)) + exit(1) + exit(0) +print("NO ICMP PARAMETER PROBLEM") +exit(2) -- 2.20.1