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

nat_del_dispatch never frees nat_state2 entries (free loops commented out): unbounded kernel heap leak on NAT delete/flush

Summary

nat_del_dispatch frees only the RB-tree nat_states; the loops meant to free per-alias nat_state2 entries are commented out, so every tcp_in/udp_in/icmp_in state (up to 3x64511 per alias) leaks on 'ipfw3 nat N delete'/flush/module unload. The cleanup callout only reaps s2 while the NAT exists. The icmp_in store also overwrites colliding s2 without freeing. Root config-churn leaks 28B x states unboundedly. Code-certain (Low). Fix: walk the alias arrays and kfree non-NULL entries before freeing the alias; free displaced s2 on overwrite.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3400 Β· 2 files
FileTypeDescriptionSize
README.md β€” 1.5 KB ↓ raw
verdict.json β€” 1.6 KB view raw

DF-3400 β€” ipfw3 NAT delete leaks every nat_state2 (and icmp_in overwrite never frees)

What

sys/net/ipfw3_nat/ip_fw3_nat.c:806-842 (nat_del_dispatch): the loops that were supposed to free the per-alias nat_state2 entries (tcp_in[], udp_in[], icmp_in[]) are commented out β€” only the RB-tree nat_states are freed before kfree(alias)/kfree(nat):

/*
LIST_FOREACH_MUTABLE(s2, &nat->alias->icmp_in, next, tmp2) { ... }
*/

Every nat_state2 created while the NAT was active (one per flow per direction; up to 3 Γ— 64511 per alias) is leaked on ipfw3 nat N delete / ipfw3 nat flush / module unload. The daily cleanup callout frees expired s2s only while the NAT still exists (:948-996); deleting the NAT first strands them all.

Additionally :326's store (alias->icmp_in[s->alias_port] = s2) overwrites any previous s2 at the same residue without freeing it (two internal hosts whose src*dst collide) β€” a slow steady leak even without deletes.

Impact

Kernel heap leak owned entirely by root configuration churn + traffic: add β†’ traffic β†’ delete cycles leak 28 B Γ— states. Unbounded with repeated reconfiguration. Low severity (root-gated), certainty: certain from code; not verified on the guest (Low β†’ Phase V skipped per contract).

Fix

Uncomment/rewrite the s2 free loops in nat_del_dispatch to walk alias->{tcp,udp,icmp}_in[] and kfree every non-NULL entry before freeing the alias, and free the displaced s2 when overwriting a non-NULL icmp_in slot at :326.

Fix verification

not_testable
per-fix-DF-3400

Confirmed kernel references

Detail

Evidence (decisive lines)

['findings/poc/DF-3400/README.md']

PoC changes

n/a (Phase V skipped for this Low finding per batch contract)

Verified recommended fix

Uncomment/rewrite the s2 free loops in nat_del_dispatch to walk alias->{tcp,udp,icmp}_in[] and kfree non-NULL entries before freeing the alias; free displaced s2 on icmp_in overwrite

Verdict

nat_del_dispatch (ip_fw3_nat.c:785-850) frees only the RB-tree nat_states; the loops meant to free per-alias nat_state2 entries are commented out (:806-842), so every tcp_in/udp_in/icmp_in state (one per flow per direction, up to 3x64511 per alias) leaks on 'ipfw3 nat N delete'/flush/module unload. The cleanup callout frees expired s2 only while the NAT exists (:948-996), so deleting first strands them. Additionally :326's icmp_in store overwrites a colliding s2 without freeing. Root-gated config-churn heap leak, unbounded with add/delete cycles. Certain from code; Phase V skipped (Low, per batch contract).