Unreplied cross-CPU netmsg in NAT state-add path leaks the kmalloc'd message when dispatch CPU diverges from hash CPU
Summary
The state-add path kmallocs a netmsg_nat_state_add and netisr_sendmsg()s it, but nat_state_add_dispatch never calls netisr_replymsg - only netisr_afree_rport replies kfree; a reply to td_msgport never frees. One ~80B message leaked per new NAT flow whenever nextcpu != mycpuid. On the guest the branch provably never fires (udp_output re-dispatches to netisr_hashcpu before ip_output; same-hash for TCP/forwarded): 36k NAT'd flows left lwkt_message in-use flat at 2 - not_reproduced; reachable in principle when a NIC RSS hash diverges from ipfw3's software recompute. Fix: netmsg_init with netisr_afree_rport + netisr_replymsg.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3399 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| udp_spray.c | β | 1.3 KB | view raw | |
| leak.sh | β | 932 B | view raw | |
| run.log | β | 591 B | view raw |
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
["findings/poc/DF-3399/run.log: vmstat -m lwkt_message Count stays 2 across 36k NAT'd flows (baseline + isolation spray)", 'sys/net/ipfw3_nat/ip_fw3_nat.c:384-407 netmsg kmalloc+sendmsg; :701-725 handler without netisr_replymsg/forwardmsg', 'sys/netinet/udp_usrreq.c:1136-1160 redispatch-to-hash-cpu before ip_output (why the guest never crosses CPUs)']
PoC changes
wrote udp_spray.c (fresh source port per datagram to force a new 5-tuple/state per send); leak.sh harness using vmstat -m per malloc-type
Verified recommended fix
netmsg_init with &netisr_afree_rport and terminate nat_state_add_dispatch with netisr_replymsg(&add_msg->base, 0)
Verdict
nat_state_add_dispatch (ip_fw3_nat.c:701-725) never replies or forwards the kmalloc'd netmsg_nat_state_add, which per the lwkt/netisr API (only netisr_afree_rport replies kfree the message, netisr.c:138-142) means the ~80B message can never be freed -- a per-new-flow kernel heap leak whenever the branch runs. On the 6-vCPU verification guest the branch could not be exercised: udp_output re-dispatches to netisr_hashcpu(udp_addrhash()) before ip_output (udp_usrreq.c:1136-1160) and forwarded packets are dispatched by the same ip_hashfn, so nextcpu == mycpuid deterministically; 36,000 NAT'd UDP flows left the lwkt_message malloc-type in-use count flat at 2 (run.log), and an isolation spray with the NAT rule deleted showed the per-flow Requests growth is the plain UDP path, not ipfw3. The leak is reachable in principle when a NIC RSS hash (M_HASH set by the driver) diverges from ipfw3's software recompute (ip_fw3_nat.c:379-380 clears M_HASH and recomputes), leaking one netmsg per new NAT flow -- gradual, root-config-gated memory exhaustion. Filed Low/likely: API violation certain, trigger narrow and not present on this guest.
No comments yet.