/*
 * DF-2575 PoC — ipfw3 dummynet dn_priv use-after-free.
 *
 * Bug (sys/net/ipfw3/ip_fw3.c:630):
 *   ip_fw3_dummynet_io() stores a raw, unrefcounted pointer to the matching
 *   ipfw3 rule into the dummynet packet tag:
 *
 *       pkt->dn_priv = fwa->rule;          // line 630
 *
 *   dn_unref_priv is left NULL (the struct is bzero'd at line 604), so the
 *   rule reference is never accounted for.  ip_fw3_delete_rule() (line 757)
 *   unlinks and kfrees the rule slab (M_IPFW3) on every CPU via
 *   delete_rule_dispatch() WITHOUT sweeping dummynet tags that still hold the
 *   dangling dn_priv pointer.
 *
 *   When the dummynet pipe expires and re-injects the queued packet,
 *   ip_fw3_check_in/out() (lines 1191 / 1266) loads the stale pointer:
 *
 *       args.rule = ((struct dn_pkt *)m_tag_data(mtag))->dn_priv;  // UAF load
 *
 *   and, when net.inet.ip.fw3.one_pass == 0 (ip_fw3.c:428), ip_fw3_chk()
 *   dereferences it:
 *
 *       f = args->rule->next_rule;     // UAF DEREF — freed M_IPFW3 slab
 *
 *   The freed slab is poisoned 0xdeadc0de under INVARIANTS → page fault →
 *   kernel panic.  Without INVARIANTS the freed slot is uninitialised heap
 *   and the deref yields an attacker-influenceable pointer (the basis for a
 *   heap-grooming escalation, though this path is root-only — see below).
 *
 * Privilege gate: the entire path is root-only.
 *   - kldload ipfw3 / ipfw3_basic / dummynet3     → root
 *   - sysctl net.inet.ip.fw3.one_pass              → root (CTLFLAG_RW)
 *   - ipfw3 add / ipfw3 delete (raw-socket setsockopt IP_FW_X)
 *       → rip_ctloutput → caps_priv_check(SYSCAP_NONET_RAW) → root
 *   So this is a root→kernel memory-corruption bug.  Root→kernel is game-over
 *   by definition (kldload), so uid0 escalation from an unprivileged user is
 *   NOT possible here.  Impact = root-triggerable kernel panic / DoS (and a
 *   defence-in-depth hardening gap: dn_unref_priv should be used).
 *
 * Trigger recipe (race):
 *   1. pipe 1 configured with multi-second delay  → packets SIT in the pipe
 *      queue holding dn_priv = rule pointer.
 *   2. one_pass = 0  → re-injected packets re-traverse and DEREF args.rule.
 *   3. ICMP rule matching 127.0.0.1 → safe scope (lo0, not the ssh vtnet0 path).
 *   4. ping -f 127.0.0.1 floods packets into the pipe.
 *   5. delete the rule → kfree(rule) on all CPUs while dn_priv dangles.
 *   6. pipe delay expires → dummynet re-injects → ip_fw3_chk derefs freed rule.
 *
 * Build:  nothing to compile — pure driver shell script (poc.sh).
 *         (This .c file is documentation; the trigger is poc.sh.)
 * Run:    see run.sh  (as root on the guest)
 */
