Test pf and stack with double atomic IPv6 fragments.
authorbluhm <bluhm@openbsd.org>
Fri, 8 Sep 2023 21:15:02 +0000 (21:15 +0000)
committerbluhm <bluhm@openbsd.org>
Fri, 8 Sep 2023 21:15:02 +0000 (21:15 +0000)
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
regress/sys/netinet6/frag6/frag6_doubleatomic.py [new file with mode: 0644]

index 338fc9c..8d1436c 100644 (file)
@@ -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 (file)
index 0000000..dcffc55
--- /dev/null
@@ -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)