DF-3439 / run.log
================================================================
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.