pf: broken RB comparator drops IPv6 fragments

Renaud Allard <[email protected]>
Newsgroups gmane.os.openbsd.bugs
Message-ID <[email protected]>
pf_frag_compare() in sys/net/pf_norm.c uses subtraction to compare
u_int32_t fragment IDs:

	if ((diff = a->fr_id - b->fr_id) != 0)
		return (diff);

diff is int.  When the unsigned difference exceeds INT_MAX the
signed result wraps, violating both antisymmetry and transitivity.
IPv4 is unaffected (ip_id is 16-bit).  IPv6 uses the full 32-bit
ip6f_ident, attacker-controlled.

fr_id was widened from u_int16_t to u_int32_t in 9ba4cdcc9cc
(2011-03-23) without updating the comparator.

Tested on OpenBSD 7.9-beta amd64 with default pf config.  Injected
32 IPv6 fragment pairs via BPF on lo0 with IDs spanning the full
32-bit range: all first-fragments first (building a 32-entry RB
tree), then all second-fragments (triggering lookups).

  Stock kernel (GENERIC#338):
    Run 1: 13/32 reassembled (19 silently dropped)
    Run 2: 29/32
    Run 4: 31/32
    Run 5: 31/32

  Patched kernel (GENERIC#0, fix applied):
    Run 1: 32/32
    Run 2: 32/32
    Run 3: 32/32
    Run 4: 32/32
    Run 5: 32/32

Index: sys/net/pf_norm.c
===================================================================
--- sys/net/pf_norm.c
+++ sys/net/pf_norm.c
@@ -187,10 +187,10 @@ pf_frnode_compare(struct pf_frnode *a, s
 static __inline int
 pf_frag_compare(struct pf_fragment *a, struct pf_fragment *b)
 {
-	int	diff;
-
-	if ((diff = a->fr_id - b->fr_id) != 0)
-		return (diff);
+	if (a->fr_id > b->fr_id)
+		return (1);
+	if (a->fr_id < b->fr_id)
+		return (-1);

 	return (0);
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.