================================================================ DF-3439 — ipfw3 skipto next_rule stale-cache use-after-free Guest: DragonFly 6.5-DEVELOPMENT #0 (stock INVARIANTS) — baseline runs ================================================================ == Phase A (setup, every run) == # sysctl -w net.filters_default_to_accept=1 (BEFORE kldload, so the # default rule 65535 is ACCEPT and ssh survives the firewall) # kldload ipfw3 && kldload ipfw3_basic console: ipfw3 initialized, default to accept ipfw3 module basic loaded == Run 1 — behavioral (misencoded-rule era, mechanism still valid) == added rule 100: skipto 200 / added rule 200: proto 1 deny ping (rules live) : 5 tx, 5 rx, 0% loss (cache pointed at default from the add-window: skipto never reached 200) ipfw3x del 200 ping (after delete) : "ping: sendto: Permission denied" x5, 100% loss => after deleting the ONLY deny rule, on a default-ACCEPT chain, ICMP is still denied: the stale next_rule cache executes the freed rule-200 chunk. Minutes later the console flooded: "+++ ipfw: ouch!, skip past end of rules, denying packet" (9+ lines, see blackhole_console.txt) and ssh died: freed chunk reused by an ordinary compile; every packet walking the stale cache got denied. == Run 2 — corrected encoding, live-memory dumps (krules via /dev/kmem) == added rule 200: proto 1 deny (4 words) added rule 100: skipto 200 ping (rules live): 4 tx, 0 rx, 100% loss [deny works, no panic] pre-delete dump (cpu0): rule ...ba40 num=100 set=0 act_ofs=0 cmd_len=2 pcnt=6 next=0x...4f1837a0 next_rule=0x...4f1837a0 insn0: opcode=3(skipto) len=2 arg1=200 module=0 rule ...37a0 num=200 act_ofs=2 cmd_len=4 insn0: opcode=10(proto) len=2 arg1=1 module=0 insn1: opcode=1(deny) len=2 module=0 => rule100->next_rule == &rule200 (the cache), rule200 valid. == Run 2 delete == ipfw3x del 200 => console immediately floods "ouch!, skip past end of rules, denying packet"; ssh blackholes before the post-delete dump can return. == Run 3 — poison run (debug.use_weird_array=1 before delete) == identical phases; pre-delete dump shows cpu3: rule ...4dc0? num=100 pcnt=11 next=0x...8fa26de8 next_rule=0x...8fa26de8 rule ...26de8 num=200 act_ofs=2 cmd_len=4 del 200; ping: Fatal trap 9: general protection fault while in kernel mode cpuid = 1; lapic id = 1 instruction pointer = 0x8:0xffffffff826001a4 (ip_fw3_chk+0x1a4: ret) stack pointer = 0x10:0xfffff8008d1fd760 current process = Idle (netisr) Stopped at ip_fw3_chk+0x1a4: ret (see panic.txt — the stale cache jumps into the 0xdeadc0de-poisoned freed chunk; wild filter_funcs[module][opcode] indirect call smashes the frame; GPF at the ret) ================================================================ FIX VALIDATION — kernel #1 (fix.diff: clear next_rule caches on delete) ================================================================ same phases on the patched kernel: pre-delete: ping 100% loss; dump: rule100.next_rule == &rule200 (cache populated exactly as on stock) del 200; dump: rule ...ba40 num=100 ... next_rule=0x0 <- cache CLEARED (one cpu re-cached onto the LIVE default rule after its next packet: num=100 ... next_rule=0x...16be7a80 == &default65535) ping after delete: 4 tx, 4 rx, 0% loss (fresh lookup -> default ACCEPT) no ouch flood, no panic, guest up. => baseline bad behavior gone with the single-function fix.