From nobody Tue Jan 14 10:37:54 2025 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4YXQZC3Wd5z5kVfr; Tue, 14 Jan 2025 10:37:55 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R11" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4YXQZC0gb3z3C6K; Tue, 14 Jan 2025 10:37:55 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1736851075; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=GjqkS0d5k9ld0eZS/a2Hbitae24LxgV1aeiIyzwRQnE=; b=EUM8ON/03wKWPYxXbB9ZdomOt7ObOHe5W+cUicypoDVo/nWXdCrTYmdflC8cd6UEoZgyd2 2xALHJrZNm1NUIGnoZcQYz/83I8JqrHr8DV5gY+UTRLNWjiyvcEpR22oVbu4V2pApd8jdu UbfDv99tXvPXypl5NwupMuOECattMHlPgw6Stegh4mKyHaWikXSvQr6uUG0hymZHF9HJct Nmg3wuGJi6FstStUjjigdqJgqB4JbwfBZIYS9CnVWYZHLm3rjw5aAik6XRWDle5QJHPho6 JTcbwHBIR9+b7w6iQIJR+p6pOjYFsEd60P5o2F6DMXd2UpatVJ5Up05pRF3IFA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1736851075; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=GjqkS0d5k9ld0eZS/a2Hbitae24LxgV1aeiIyzwRQnE=; b=r25sWsA0or5UnfoeP8ovxsaNERwDLUoKJjfQdoNOjYojmst7AWn1B4VSBg+o3/n2HQnva0 4PNtHf1t9eiQCFhP9VcuY3fIYsOGkvk1uz50Vczwzs6R3knHBR6x4rFRD5RJixRRjSPMZJ 62QUAsW5xPdZt3AKQrKgXwlJcL4XIyVed2T4yalkKZ41RE54e/obErHezItiB7gQJikfcD a4LV8cofBW6tsrirlvpiMffr15sPT/8HrxkZ9xpGocO900Q9ELFQHmFz7/PyAqygjhFPC8 UgoACiwqwQ7HKHBXx1FUe+qOXactMvgHREQYecZwyE2KBwkbBalOZ+x/nU+sSQ== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1736851075; a=rsa-sha256; cv=none; b=fMVSyl6VNJuj3wzmgmUtbdaG/uPyy8Y3DyEbgXSSWgnJdLANDKRUViv7VPxZPabkpRaZD5 Xslz3W+XKKgLn8OBkCedpwSSG5sXhPCZLGJHnJRzS0YCmwkgPRVlrz+pVYG5MeR3VDF5Pu epXwr/fiTrlvSZSPpTUwKthYQAu3CT2xRwUjUIsTembS3I6rCF8rGEUUl4kGE8F1TNmVJd y2+ei5pEE4TOgV92FF5mG6YgEAeGxuuvZW2zd1Si1eGlzzKlHelvFlKvj7J7qg7ggpLz69 feNjxZfzsmp0EzHELGzGa9VrK2nLuBOPhIRewA9y8scZ6C+z+nk5Qr3XaDy/Eg== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4YXQZC0GRMz1CLT; Tue, 14 Jan 2025 10:37:55 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.18.1/8.18.1) with ESMTP id 50EAbsQU048154; Tue, 14 Jan 2025 10:37:54 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.18.1/8.18.1/Submit) id 50EAbs1r048151; Tue, 14 Jan 2025 10:37:54 GMT (envelope-from git) Date: Tue, 14 Jan 2025 10:37:54 GMT Message-Id: <202501141037.50EAbs1r048151@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Kristof Provost Subject: git: 6a3266f72e43 - main - pf: drop IPv6 packets built from overlapping fragments in pf reassembly List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: kp X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 6a3266f72e437aecf3edcfb8aa919466b270d548 Auto-Submitted: auto-generated The branch main has been updated by kp: URL: https://cgit.FreeBSD.org/src/commit/?id=6a3266f72e437aecf3edcfb8aa919466b270d548 commit 6a3266f72e437aecf3edcfb8aa919466b270d548 Author: Kristof Provost AuthorDate: 2025-01-08 13:34:22 +0000 Commit: Kristof Provost CommitDate: 2025-01-14 08:54:19 +0000 pf: drop IPv6 packets built from overlapping fragments in pf reassembly The reassembly state will be dropped after timeout, all related fragments are dropped until that. This is conforming to RFC 5722. - Sort pf_fragment fields while there. - If the fr_queue is empty, we had overlapping fragments, don't add new ones. - If we detect overlapping IPv6 fragments, flush the fr_queue and drop all fragments immediately. - Rearrange debug output, to make clear what happens. - An IPv4 fragment that is totaly overlapped does not inclease the bad fragment counter. - Put an KASSERT into pf_isfull_fragment() to make sure that the fr_queue is never emtpy there. discussed with Fernando Gont; ok henning@ Obtained from: OpenBSD, bluhm , 8b45f36762 Sponsored by: Rubicon Communications, LLC ("Netgate") --- sys/netpfil/pf/pf_norm.c | 46 +++++++++++++++++++++++++++++++++++-------- tests/sys/netpfil/pf/frag6.py | 44 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/sys/netpfil/pf/pf_norm.c b/sys/netpfil/pf/pf_norm.c index 5b34e825203b..45e56edc34fe 100644 --- a/sys/netpfil/pf/pf_norm.c +++ b/sys/netpfil/pf/pf_norm.c @@ -98,9 +98,9 @@ struct pf_fragment { RB_ENTRY(pf_fragment) fr_entry; TAILQ_ENTRY(pf_fragment) frag_next; uint32_t fr_timeout; + TAILQ_HEAD(pf_fragq, pf_frent) fr_queue; uint16_t fr_maxlen; /* maximum length of single fragment */ u_int16_t fr_holes; /* number of holes in the queue */ - TAILQ_HEAD(pf_fragq, pf_frent) fr_queue; }; VNET_DEFINE_STATIC(struct mtx, pf_frag_mtx); @@ -593,9 +593,9 @@ pf_fillup_fragment(struct pf_fragment_cmp *key, struct pf_frent *frent, memset(frag->fr_firstoff, 0, sizeof(frag->fr_firstoff)); memset(frag->fr_entries, 0, sizeof(frag->fr_entries)); frag->fr_timeout = time_uptime; + TAILQ_INIT(&frag->fr_queue); frag->fr_maxlen = frent->fe_len; frag->fr_holes = 1; - TAILQ_INIT(&frag->fr_queue); RB_INSERT(pf_frag_tree, &V_pf_frag_tree, frag); TAILQ_INSERT_HEAD(&V_pf_fragqueue, frag, frag_next); @@ -606,7 +606,15 @@ pf_fillup_fragment(struct pf_fragment_cmp *key, struct pf_frent *frent, return (frag); } - KASSERT(!TAILQ_EMPTY(&frag->fr_queue), ("!TAILQ_EMPTY()->fr_queue")); + if (TAILQ_EMPTY(&frag->fr_queue)) { + /* + * Overlapping IPv6 fragments have been detected. Do not + * reassemble packet but also drop future fragments. + * This will be done for this ident/src/dst combination + * until fragment queue timeout. + */ + goto drop_fragment; + } /* Remember maximum fragment len for refragmentation. */ if (frent->fe_len > frag->fr_maxlen) @@ -642,10 +650,15 @@ pf_fillup_fragment(struct pf_fragment_cmp *key, struct pf_frent *frent, if (prev != NULL && prev->fe_off + prev->fe_len > frent->fe_off) { uint16_t precut; + if (frag->fr_af == AF_INET6) + goto flush_fragentries; + precut = prev->fe_off + prev->fe_len - frent->fe_off; - if (precut >= frent->fe_len) - goto bad_fragment; - DPFPRINTF(("overlap -%d\n", precut)); + if (precut >= frent->fe_len) { + DPFPRINTF(("new frag overlapped\n")); + goto drop_fragment; + } + DPFPRINTF(("frag head overlap %d\n", precut)); m_adj(frent->fe_m, precut); frent->fe_off += precut; frent->fe_len -= precut; @@ -664,7 +677,7 @@ pf_fillup_fragment(struct pf_fragment_cmp *key, struct pf_frent *frent, after->fe_len -= aftercut; new_index = pf_frent_index(after); if (old_index != new_index) { - DPFPRINTF(("frag index %d, new %d", + DPFPRINTF(("frag index %d, new %d\n", old_index, new_index)); /* Fragment switched queue as fe_off changed */ after->fe_off -= aftercut; @@ -676,7 +689,7 @@ pf_fillup_fragment(struct pf_fragment_cmp *key, struct pf_frent *frent, /* Insert into correct queue */ if (pf_frent_insert(frag, after, prev)) { DPFPRINTF( - ("fragment requeue limit exceeded")); + ("fragment requeue limit exceeded\n")); m_freem(after->fe_m); uma_zfree(V_pf_frent_z, after); /* There is not way to recover */ @@ -687,6 +700,7 @@ pf_fillup_fragment(struct pf_fragment_cmp *key, struct pf_frent *frent, } /* This fragment is completely overlapped, lose it. */ + DPFPRINTF(("old frag overlapped\n")); next = TAILQ_NEXT(after, fr_next); pf_frent_remove(frag, after); m_freem(after->fe_m); @@ -701,6 +715,22 @@ pf_fillup_fragment(struct pf_fragment_cmp *key, struct pf_frent *frent, return (frag); +flush_fragentries: + /* + * RFC5722: When reassembling an IPv6 datagram, if one or + * more its constituent fragments is determined to be an + * overlapping fragment, the entire datagram (and any constituent + * fragments, including those not yet received) MUST be + * silently discarded. + */ + DPFPRINTF(("flush overlapping fragments\n")); + while ((prev = TAILQ_FIRST(&frag->fr_queue)) != NULL) { + TAILQ_REMOVE(&frag->fr_queue, prev, fr_next); + + m_freem(prev->fe_m); + uma_zfree(V_pf_frent_z, prev); + } + bad_fragment: REASON_SET(reason, PFRES_FRAG); drop_fragment: diff --git a/tests/sys/netpfil/pf/frag6.py b/tests/sys/netpfil/pf/frag6.py index 28b1829d418c..f54381fba8cb 100644 --- a/tests/sys/netpfil/pf/frag6.py +++ b/tests/sys/netpfil/pf/frag6.py @@ -58,3 +58,47 @@ class TestFrag6(VnetTestTemplate): timeout=3) for p in packets: assert not p.getlayer(sp.ICMPv6EchoReply) + +class TestFrag6_Overlap(VnetTestTemplate): + REQUIRED_MODULES = ["pf"] + TOPOLOGY = { + "vnet1": {"ifaces": ["if1"]}, + "vnet2": {"ifaces": ["if1"]}, + "if1": {"prefixes6": [("2001:db8::1/64", "2001:db8::2/64")]}, + } + + def vnet2_handler(self, vnet): + ToolsHelper.print_output("/sbin/pfctl -e") + ToolsHelper.print_output("/sbin/pfctl -x loud") + ToolsHelper.pf_rules([ + "scrub fragment reassemble", + "pass", + ]) + + @pytest.mark.require_user("root") + def test_overlap(self): + "Ensure we discard packets with overlapping fragments" + + # Import in the correct vnet, so at to not confuse Scapy + import scapy.all as sp + + packet = sp.IPv6(src="2001:db8::1", dst="2001:db8::2") \ + / sp.ICMPv6EchoRequest(data=sp.raw(bytes.fromhex('f00f') * 90)) + frags = sp.fragment6(packet, 128) + assert len(frags) == 3 + + f = frags[0].getlayer(sp.IPv6ExtHdrFragment) + # Fragment with overlap + overlap = sp.IPv6(src="2001:db8::1", dst="2001:db8::2") \ + / sp.IPv6ExtHdrFragment(offset = 4, m = 1, id = f.id, nh = f.nh) \ + / sp.raw(bytes.fromhex('f00f') * 4) + frags = [ frags[0], frags[1], overlap, frags[2] ] + + # Delay the send so the sniffer is running when we transmit. + s = DelayedSend(frags) + + packets = sp.sniff(iface=self.vnet.iface_alias_map["if1"].name, + timeout=3) + for p in packets: + p.show() + assert not p.getlayer(sp.ICMPv6EchoReply)