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 <bluhm@openbsd.org>, 640736615b
Sponsored by:	Rubicon Communications, LLC ("Netgate")

(cherry picked from commit db100bd93036855c7688dc088b811dc7b660f51d)
This commit is contained in:
Kristof Provost 2025-02-04 14:06:33 +01:00 committed by Franco Fichtner
parent 5be39bc21c
commit b8ab1d06e8
4 changed files with 183 additions and 0 deletions

View File

@ -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

View File

@ -0,0 +1,58 @@
#!/usr/bin/env python3
#
# Copyright (c) 2025 Alexander Bluhm <bluhm@openbsd.org>
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)

View File

@ -0,0 +1,83 @@
#!/usr/bin/env python3
#
# Copyright (c) 2025 Alexander Bluhm <bluhm@openbsd.org>
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)

View File

@ -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"