β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3434

ipfw3 keep-state limit rules skip the duplicate check: one leaked state + one kprintf('oops') console line per packet (RB_INSERT duplicate never freed)

Summary

check_keep_state only RB_FINDs when cmd->arg3==0; limit-type rules (arg3=1..4, documented grammar) allocate unconditionally per packet and on RB_INSERT collision merely kprintf('oops') - the fresh 64B state leaks and the console is flooded at line rate by any traffic matching the rule. VERIFIED: 320 packets -> 638 oops lines, M_IPFW3_STATE inuse 46->646. Leak bounded per tree by the DF-0632 counter gate; the console flood is unbounded (console-lock amplification / serial-console stall) from unpriv local or unauth remote traffic once root installs such a rule. Fix validated (RB_FIND before alloc + refresh timestamp + kfree on collision): 0 lines / flat.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3434 Β· 8 files
FileTypeDescriptionSize
state_oops.c β€” 2.1 KB view raw
setup_root.sh β€” 335 B view raw
run.sh β€” 244 B view raw
run.log β€” 1.4 KB view raw
fix.diff β€” 1.8 KB view raw
run.patched.log β€” 340 B view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 3.5 KB view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched module on two kernels (#2 16:27:37 and #1 16:56:02): the identical trigger (320 identical UDP packets, 640 rule evaluations confirmed by ipfw3 show pcnt) produced ZERO 'oops' console lines (baseline: 638) and M_IPFW3_STATE inuse stayed at 6-8 (the per-CPU contexts only; baseline: 646 leaked). Duplicate packets now take the RB_FIND path and refresh the timestamp; no allocation, no kprintf.

['run.patched.log', 'fix.diff']
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Sat Sep 12 16:56:02 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Evidence (decisive lines)

["run.log: dmesg oops count 38 -> 638 and M_IPFW3_STATE inuse 46 -> 646 for 20+300 identical packets; 'ipfw3 show' line 00099 with 250360 matches", 'VERDICT.md: root-cause walk ip_fw3_state.c:296-330', 'run.patched.log: patched kernel - same 320-packet trigger, zero oops lines, flat M_IPFW3_STATE']

PoC changes

Setup staged through a nohup script because kldload ipfw3 engages default-deny instantly and severs the staging ssh session; trigger runs unprivileged (run_user).

Verified recommended fix

RB_FIND before allocating in check_keep_state(); kfree on RB_INSERT collision instead of kprintf (fix.diff).

Verdict

Deterministic on the stock INVARIANTS guest. With root having installed 'allow udp from any to any keep-state limit src-port 100' (documented ipfw3 grammar, lib/libipfw3/basic/ipfw3_basic.c:562-574 -> cmd->arg3=2), 320 identical UDP packets from an unprivileged fixed-port socket produced 638 'oops' console lines (dmesg) and grew M_IPFW3_STATE inuse 46 -> 646: check_keep_state() skips the RB_FIND de-dup for arg3!=0 (ip_fw3_state.c:296-300), allocates a fresh state per packet (:319), and on the RB_INSERT duplicate (:327) only kprintf('oops') (:328) - the allocation is leaked. Two effects per packet at line rate from any traffic the rule matches: bounded-per-tree 64B kernel memory leak (capped only by the never-decremented count gate, DF-0632) and an unbounded serial-console kprintf flood (console-lock amplification). Root configures the rule; unprivileged local users or unauthenticated remote peers trigger it.