From b8ab1d06e8048a68c09f4e182868d2d5f06f6263 Mon Sep 17 00:00:00 2001 From: Kristof Provost Date: Tue, 4 Feb 2025 14:06:33 +0100 Subject: [PATCH] pf tests: add more fragmentation test cases Add more test cases for pf fragment hole counter. Also look into final fragment of echo reply and check total length of IP packet. MFC after: 1 week Obtained from: OpenBSD, bluhm , 640736615b Sponsored by: Rubicon Communications, LLC ("Netgate") (cherry picked from commit db100bd93036855c7688dc088b811dc7b660f51d) --- tests/sys/netpfil/pf/Makefile | 4 ++ tests/sys/netpfil/pf/frag-adjhole.py | 58 +++++++++++++++ tests/sys/netpfil/pf/frag-overhole.py | 83 ++++++++++++++++++++++ tests/sys/netpfil/pf/fragmentation_pass.sh | 38 ++++++++++ 4 files changed, 183 insertions(+) create mode 100644 tests/sys/netpfil/pf/frag-adjhole.py create mode 100644 tests/sys/netpfil/pf/frag-overhole.py diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile index dc77fd67b2c..f2ccd5aa6ec 100644 --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -62,6 +62,8 @@ ${PACKAGE}FILES+= CVE-2019-5597.py \ frag-overindex.py \ frag-overlimit.py \ frag-overreplace.py \ + frag-overhole.py \ + frag-adjhole.py \ pfsync_defer.py \ pft_ether.py \ rdr-srcport.py \ @@ -73,6 +75,8 @@ ${PACKAGE}FILESMODE_fragcommon.py= 0555 ${PACKAGE}FILESMODE_frag-overindex.py= 0555 ${PACKAGE}FILESMODE_frag-overlimit.py= 0555 ${PACKAGE}FILESMODE_frag-overreplace.py= 0555 +${PACKAGE}FILESMODE_frag-overhole.py= 0555 +${PACKAGE}FILESMODE_frag-adjhole.py= 0555 ${PACKAGE}FILESMODE_pfsync_defer.py= 0555 ${PACKAGE}FILESMODE_pft_ether.py= 0555 diff --git a/tests/sys/netpfil/pf/frag-adjhole.py b/tests/sys/netpfil/pf/frag-adjhole.py new file mode 100644 index 00000000000..99caf66617d --- /dev/null +++ b/tests/sys/netpfil/pf/frag-adjhole.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2025 Alexander Bluhm + +from fragcommon import * + +# |--------| +# |--------| +# |-------| +# |----| + +def send(src, dst, send_if, recv_if): + pid = os.getpid() + eid = pid & 0xffff + payload = b"ABCDEFGHIJKLMNOP" * 2 + packet = sp.IP(src=src, dst=dst)/ \ + sp.ICMP(type='echo-request', id=eid) / payload + frag = [] + fid = pid & 0xffff + frag.append(sp.IP(src=src, dst=dst, proto=1, id=fid, + flags='MF') / bytes(packet)[20:36]) + frag.append(sp.IP(src=src, dst=dst, proto=1, id=fid, + frag=2, flags='MF') / bytes(packet)[36:52]) + frag.append(sp.IP(src=src, dst=dst, proto=1, id=fid, + frag=1, flags='MF') / bytes(packet)[28:44]) + frag.append(sp.IP(src=src, dst=dst, proto=1, id=fid, + frag=4) / bytes(packet)[52:60]) + eth=[] + for f in frag: + eth.append(sp.Ether()/f) + if os.fork() == 0: + time.sleep(1) + sp.sendp(eth, iface=send_if) + os._exit(0) + + ans = sp.sniff(iface=recv_if, timeout=3, filter= + "ip and src " + dst + " and dst " + src + " and icmp") + for a in ans: + if a and a.type == sp.ETH_P_IP and \ + a.payload.proto == 1 and \ + a.payload.frag == 0 and a.payload.flags == 0 and \ + sp.icmptypes[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.payload.load + print("payload=%s" % (data)) + if data == payload: + exit(0) + print("PAYLOAD!=%s" % (payload)) + exit(1) + print("NO ECHO REPLY") + exit(2) + +if __name__ == '__main__': + main(send) diff --git a/tests/sys/netpfil/pf/frag-overhole.py b/tests/sys/netpfil/pf/frag-overhole.py new file mode 100644 index 00000000000..91697b6b83c --- /dev/null +++ b/tests/sys/netpfil/pf/frag-overhole.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2025 Alexander Bluhm + +from fragcommon import * + +# index boundary 4096 | +# |--------------| +# .... +# |--------------| +# |----------| +# |XXXX----------| +# |XXXX----| +# |---| + +# this should trigger "frag tail overlap %d" and "frag head overlap %d" +def send(src, dst, send_if, recv_if): + pid = os.getpid() + eid = pid & 0xffff + payload = b"ABCDEFGHIJKLMNOP" + dummy = b"01234567" + fragsize = 1024 + boundary = 4096 + fragnum = int(boundary / fragsize) + packet = sp.IP(src=src, dst=dst)/ \ + sp.ICMP(type='echo-request', id=eid)/ \ + ((int((boundary + fragsize) / len(payload)) + 1) * payload) + packet_length = len(packet) + frag = [] + fid = pid & 0xffff + for i in range(fragnum-1): + frag.append(sp.IP(src=src, dst=dst, proto=1, id=fid, + frag=(i * fragsize)>>3, flags='MF')/ + bytes(packet)[20 + i * fragsize:20 + (i + 1) * fragsize]) + frag.append(sp.IP(src=src, dst=dst, proto=1, id=fid, + frag=(boundary - fragsize) >> 3, flags='MF')/ + bytes(packet)[20 + boundary - fragsize:20 + boundary - len(dummy)]) + frag.append(sp.IP(src=src, dst=dst, proto=1, id=fid, + frag=(boundary - len(dummy)) >> 3, flags='MF')/ + (dummy+bytes(packet)[20 + boundary:20 + boundary + fragsize])) + frag.append(sp.IP(src=src, dst=dst, proto=1, id=fid, + frag=(boundary - 8 - len(dummy)) >> 3, flags='MF')/ + (dummy+bytes(packet)[20 + boundary - 8:20 + boundary])) + frag.append(sp.IP(src=src, dst=dst, proto=1, id=fid, + frag=(boundary + fragsize) >> 3)/bytes(packet)[20 + boundary + fragsize:]) + eth=[] + for f in frag: + eth.append(sp.Ether() / f) + + if os.fork() == 0: + time.sleep(1) + for e in eth: + sp.sendp(e, iface=send_if) + time.sleep(0.001) + os._exit(0) + + ans = sp.sniff(iface=recv_if, timeout=3, filter= + "ip and src " + dst + " and dst " + src + " and icmp") + for a in ans: + if a and a.type == sp.ETH_P_IP and \ + a.payload.proto == 1 and \ + a.payload.frag == 0 and \ + sp.icmptypes[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) + if a and a.type == sp.ETH_P_IP and \ + a.payload.proto == 1 and \ + a.payload.frag > 0 and \ + a.payload.flags == '': + length = (a.payload.frag << 3) + a.payload.len + print("len=%d" % (length)) + if length != packet_length: + print("WRONG ECHO REPLY LENGTH") + exit(1) + exit(0) + print("NO ECHO REPLY") + exit(1) + +if __name__ == '__main__': + main(send) diff --git a/tests/sys/netpfil/pf/fragmentation_pass.sh b/tests/sys/netpfil/pf/fragmentation_pass.sh index 5e92bb2d9a9..66fe336dff3 100644 --- a/tests/sys/netpfil/pf/fragmentation_pass.sh +++ b/tests/sys/netpfil/pf/fragmentation_pass.sh @@ -285,6 +285,42 @@ overlimit_cleanup() pft_cleanup } +atf_test_case "overhole" "cleanup" +overhole_head() +{ + atf_set descr 'ping fragment at index boundary which modifies pf hole counter' + atf_set require.user root + atf_set require.progs scapy +} + +overhole_body() +{ + frag_common overhole +} + +overhole_cleanup() +{ + pft_cleanup +} + +atf_test_case "adjhole" "cleanup" +adjhole_head() +{ + atf_set descr 'overlapping ping fragments which modifies pf hole counter' + atf_set require.user root + atf_set require.progs scapy +} + +adjhole_body() +{ + frag_common adjhole +} + +adjhole_cleanup() +{ + pft_cleanup +} + atf_test_case "reassemble" "cleanup" reassemble_head() { @@ -476,6 +512,8 @@ atf_init_test_cases() atf_add_test_case "overreplace" atf_add_test_case "overindex" atf_add_test_case "overlimit" + atf_add_test_case "overhole" + atf_add_test_case "adjhole" atf_add_test_case "reassemble" atf_add_test_case "no_df" atf_add_test_case "reassemble_slowpath"