From nobody Tue Feb 18 17:43:13 2025 X-Original-To: dev-commits-src-branches@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 4Yy6Lp0c3Gz5nx00; Tue, 18 Feb 2025 17:43:14 +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 4Yy6Ln5wWTz3WYN; Tue, 18 Feb 2025 17:43:13 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1739900593; 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=hdy36wdjWeor658kvqjEtOEQHjrdhW1MEnOk5gmbmH0=; b=oZSf7Y88SqU0zV4Dx7gRJgGEg8Ha5HJg+97yasDjrNmAsX6e7bhNjfRTRgbxCckD1Zji5b N6TdK8ZLaukzkl1t721/Z8wdnq8MkVUq1iv7Fg2xC6xLA/LXrVqUnMUR5bs7Dl/Suy/xB7 H8PV8pdyciCQveha7Gq1Rb9jcd1yWEJDspDVphkmQjzb2Umoj+m2yElUuao5bpBX+M6V9A /6wyK5DXlj4Dtlb5NNjPjwemyXDqIixIzIT7uUffaXvLXge9U15PsUANfzcDZweZorlj5V T9+47hxy/EjvrUVycLcdYl9kLjPVZWyJ8dCS3oosZQVIDtm7I4orBW+3IP1Piw== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1739900593; a=rsa-sha256; cv=none; b=Ply9TLgXXpBvgVnmyKiI7Q87z9IxWaSAW51PCrBv5Q1kP/MZ4LlGkvweWSnv46L5zAHV7J nmmoUv852OKNS+yrr1MSTUjOPUy9o1I5TqeUnXJf2rvi0Det0TePlYLgTV8MHk5IsKEyrl xPW670+xCkohxWMw94d+j78UuvY+M7JS8fknHWVCaIPamZk1d4dDaah38ZIClOXUp9tRxd 71jjednrG3ITEUW/k7Y4LnufMFu8tEsEXWHWQ1+PU5/ciW39YrLdYLIvUADTwOIwyc/RYl RnSkAZ7R2uPFgvYnRI94vWfkyOXBhB9ysSCe0KeIbOExdULZN1B37PikTj+XqQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1739900593; 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=hdy36wdjWeor658kvqjEtOEQHjrdhW1MEnOk5gmbmH0=; b=gIq4h7cMa6q/NJSsno5XKcub1ABUn+bewFbOkpOO80hlNmWTn61CZ6ChzeTsD6IVXZfw1e 7mJWbXHLET4behWA/lBJ/UgqhQBSIb/tcx5n6voc1BwAevPCNz0oNAey/iOGxM/8gpag0L 5uNTHTeBTeuvKM0m8G6Tr2g5oWhOZyJaX2wYaDgDsvDETNlwceotV3L1pcZVofcWhXmdrJ 3tBKe0UdJGVpdjZW8w6fIift6B5hxWwv2Rc7E3sgt6O2siFE2SwoALdKu5cQTsPIxbh9nf dlbRxG4KeNeOQyYR4BCVeIF+kgc2v1o6c2X4Ta8/N/75c7t3CpY6cBVQmSrBIQ== 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 4Yy6Ln4VdWzTyD; Tue, 18 Feb 2025 17:43:13 +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 51IHhDOX021214; Tue, 18 Feb 2025 17:43:13 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.18.1/8.18.1/Submit) id 51IHhDng021211; Tue, 18 Feb 2025 17:43:13 GMT (envelope-from git) Date: Tue, 18 Feb 2025 17:43:13 GMT Message-Id: <202502181743.51IHhDng021211@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Kristof Provost Subject: git: ea54fe79b464 - stable/14 - pf: drop IPv6 packets built from overlapping fragments in pf reassembly List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-branches@freebsd.org Sender: owner-dev-commits-src-branches@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/stable/14 X-Git-Reftype: branch X-Git-Commit: ea54fe79b46451b571050fbe05e282cd8496607d Auto-Submitted: auto-generated The branch stable/14 has been updated by kp: URL: https://cgit.FreeBSD.org/src/commit/?id=ea54fe79b46451b571050fbe05e282cd8496607d commit ea54fe79b46451b571050fbe05e282cd8496607d Author: Kristof Provost AuthorDate: 2025-01-08 13:34:22 +0000 Commit: Kristof Provost CommitDate: 2025-02-18 17:40:26 +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") (cherry picked from commit 6a3266f72e437aecf3edcfb8aa919466b270d548) --- 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 de4df7ebf4de..69548e6a997f 100644 --- a/sys/netpfil/pf/pf_norm.c +++ b/sys/netpfil/pf/pf_norm.c @@ -95,9 +95,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; }; struct pf_fragment_tag { @@ -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)