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);
}