From 45550d4a98dbed629b022333d58322e23454c128 Mon Sep 17 00:00:00 2001 From: bluhm Date: Fri, 8 Sep 2023 21:15:02 +0000 Subject: [PATCH] Test pf and stack with double atomic IPv6 fragments. That means the IPv6 header chain contains two fragment header that spawn the whole packet. Such packets are illegal and pf drops them. Otherwise they could bypass pf rules as described in CVE-2023-4809. OpenBSD is not affected as pf_walk_header6() drops them with "IPv6 multiple fragment" log message. This check exists since 2013 when special support for atomic fragments was added to pf. If pf is disabled, the IPv6 stack accepts such packets. I do not consider this a security issue. --- regress/sys/netinet6/frag6/Makefile | 6 ++- .../sys/netinet6/frag6/frag6_doubleatomic.py | 49 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 regress/sys/netinet6/frag6/frag6_doubleatomic.py diff --git a/regress/sys/netinet6/frag6/Makefile b/regress/sys/netinet6/frag6/Makefile index 338fc9cc44b..8d1436c4f65 100644 --- a/regress/sys/netinet6/frag6/Makefile +++ b/regress/sys/netinet6/frag6/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.28 2020/12/30 21:40:33 kn Exp $ +# $OpenBSD: Makefile,v 1.29 2023/09/08 21:15:02 bluhm Exp $ # The following ports must be installed: # @@ -91,6 +91,10 @@ run-stack-frag6_queuelimit.py: # the stack does not limit the amount of fragments during reassembly @echo DISABLED +run-stack-frag6_doubleatomic.py: addr.py stamp-stack + # IPv6 stack accepts double atomic fragement, this is not a big issue + set +e; ${SUDO} ${PYTHON}frag6_doubleatomic.py; [[ $$? == 1 ]] + .for sp in stack pf # Ping all addresses. This ensures that the ip addresses are configured diff --git a/regress/sys/netinet6/frag6/frag6_doubleatomic.py b/regress/sys/netinet6/frag6/frag6_doubleatomic.py new file mode 100644 index 00000000000..dcffc55cd0f --- /dev/null +++ b/regress/sys/netinet6/frag6/frag6_doubleatomic.py @@ -0,0 +1,49 @@ +#!/usr/local/bin/python3 + +print("ping6 fragments with two consecutive atomic fragment header") + +# |-IPv6-|-Frag-|-Frag-|-ICMP6-|-payload-| + +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=payload) +frag=[] +fid=pid & 0xffffffff +frag.append(IPv6ExtHdrFragment(id=fid)/ \ + IPv6ExtHdrFragment(nh=58, id=fid)/bytes(packet)[40:64]) +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: + if a and a.type == ETH_P_IPV6 and \ + ipv6nh[a.payload.nh] == 'ICMPv6' and \ + icmp6types[a.payload.payload.type] == 'Echo Reply': + id=a.payload.payload.id + print("id=%#x" % (id)) + if id != eid: + print("WRONG ECHO REPLY ID") + exit(2) + data=a.payload.payload.data + print("payload=%s" % (data)) + if data == payload: + print("double atomic accepted") + exit(1) + print("PAYLOAD!=%s" % (payload)) + exit(2) +print("no echo reply") +exit(0) -- 2.20.1