git: 7f846fc0e7ce - main - pf tests: reproduce use-after-free in fragment reassembly

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Fri, 17 Jan 2025 16:02:15 UTC
The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=7f846fc0e7ce30c80e3265c957a138d7192af397

commit 7f846fc0e7ce30c80e3265c957a138d7192af397
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2025-01-06 10:48:40 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2025-01-17 16:00:41 +0000

    pf tests: reproduce use-after-free in fragment reassembly
    
    Produce an IPv6 packet that's longer than 65535 bytes so it'll get dropped in
    pf_reassemble6(). This can then causes pf_normalize_ip6() to return an error,
    which led pf_setup_pdesc() to fail to update *m0, eventually ending up with
    pf_scrub() attempting to modify *m0 (now different from pd->m), a freed mbuf.
    
    This does depend on pf_join_fragment()'s call to m_cat() freeing the relevant
    mbuf rather than adding it to the chain. Accomplish this by ensuring there's
    sufficient free space, by having dummymbuf re-allocate larger mbufs for our
    fragments.
    
    PR:             283705
    Reported by:    Yichen Chai <yichen.chai@gmail.com>, Zhuo Ying Jiang Li <zyj20@cl.cam.ac.uk>
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
---
 tests/sys/netpfil/pf/frag6.py | 40 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

diff --git a/tests/sys/netpfil/pf/frag6.py b/tests/sys/netpfil/pf/frag6.py
index f54381fba8cb..f274fc28a3bf 100644
--- a/tests/sys/netpfil/pf/frag6.py
+++ b/tests/sys/netpfil/pf/frag6.py
@@ -2,6 +2,7 @@ import pytest
 import logging
 import threading
 import time
+import random
 logging.getLogger("scapy").setLevel(logging.CRITICAL)
 from atf_python.sys.net.tools import ToolsHelper
 from atf_python.sys.net.vnet import VnetTestTemplate
@@ -19,7 +20,7 @@ class DelayedSend(threading.Thread):
         sp.send(self._packet)
 
 class TestFrag6(VnetTestTemplate):
-    REQUIRED_MODULES = ["pf"]
+    REQUIRED_MODULES = ["pf", "dummymbuf"]
     TOPOLOGY = {
         "vnet1": {"ifaces": ["if1"]},
         "vnet2": {"ifaces": ["if1"]},
@@ -27,12 +28,15 @@ class TestFrag6(VnetTestTemplate):
     }
 
     def vnet2_handler(self, vnet):
+        ifname = vnet.iface_alias_map["if1"].name
         ToolsHelper.print_output("/sbin/pfctl -e")
         ToolsHelper.pf_rules([
-            "scrub fragment reassemble",
+            "scrub fragment reassemble min-ttl 10",
             "pass",
             "block in inet6 proto icmp6 icmp6-type echoreq",
         ])
+        ToolsHelper.print_output("/sbin/pfilctl link -i dummymbuf:inet6 inet6")
+        ToolsHelper.print_output("/sbin/sysctl net.dummymbuf.rules=\"inet6 in %s enlarge 3000;\"" % ifname)
 
     def check_ping_reply(self, packet):
         print(packet)
@@ -59,6 +63,38 @@ class TestFrag6(VnetTestTemplate):
         for p in packets:
             assert not p.getlayer(sp.ICMPv6EchoReply)
 
+    @pytest.mark.require_user("root")
+    def test_overlong(self):
+        "Test overly long fragmented packet"
+
+        # Import in the correct vnet, so at to not confuse Scapy
+        import scapy.all as sp
+
+        curr = 0
+        pkts = []
+
+        frag_id = random.randint(0,0xffffffff)
+        gran = 1200
+
+        i = 0
+        while curr <= 65535:
+            ipv61 = sp.IPv6(src="2001:db8::1", dst="2001:db8::2")
+            more = True
+            g = gran
+            if curr + gran > 65535:
+                more = False
+                g = 65530 - curr
+            if i == 0:
+                pkt = ipv61 / sp.IPv6ExtHdrHopByHop(options=[sp.PadN(optlen=2), sp.Pad1()]) / \
+                    sp.IPv6ExtHdrFragment(id = frag_id, offset = curr // 8, m = more) / bytes([i] * g)
+            else:
+                pkt = ipv61 / sp.IPv6ExtHdrFragment(id = frag_id, offset = curr // 8, m = more) / bytes([i] * g)
+            pkts.append(pkt)
+            curr += gran
+            i += 1
+
+        sp.send(pkts, inter = 0.1)
+
 class TestFrag6_Overlap(VnetTestTemplate):
     REQUIRED_MODULES = ["pf"]
     TOPOLOGY = {