diff --git a/sys/netpfil/pf/pf_norm.c b/sys/netpfil/pf/pf_norm.c index 5b34e825203..45e56edc34f 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 28b1829d418..f54381fba8c 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)