โฌข DragonFlyBSD Kernel Audit
DF-3439 / run.log
โ† back to finding โ†“ download raw
================================================================
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.