Skip to content

Commit

Permalink
pf: drop IPv6 packets built from overlapping fragments in pf reassembly
Browse files Browse the repository at this point in the history
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 <bluhm@openbsd.org>, 8b45f36762
Sponsored by:	Rubicon Communications, LLC ("Netgate")

(cherry picked from commit 6a3266f72e437aecf3edcfb8aa919466b270d548)
  • Loading branch information
kprovost authored and fichtner committed Feb 25, 2025
1 parent e47898e commit 7386d91
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 8 deletions.
46 changes: 38 additions & 8 deletions sys/netpfil/pf/pf_norm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 */
Expand All @@ -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);
Expand All @@ -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:
Expand Down
44 changes: 44 additions & 0 deletions tests/sys/netpfil/pf/frag6.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,47 @@ def test_dup_frag_hdr(self):
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)

0 comments on commit 7386d91

Please sign in to comment.