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)
PoC verification
Evidence pack
findings/poc/DF-3434 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
fixedPatched 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']
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.
No comments yet.