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

Reply via email to