DF-2575 / poc.c
/* * 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) */ |