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

kernel panic on any non-TCP/UDP/ICMP packet hitting NAT rule

Summary

Both switch statements in ip_fw3_nat(): default: panic("ipfw3: unsupported proto %u",ip->ip_p)(:219/:254). Any IP packet non-TCP/UDP/ICMP (SCTP 132 GRE 47 ESP 50 UDPLite 136 IGMP) matching NAT rule crashes kernel. Outbound path(:254) has no alias gate before switch so any internal host emitting such protocol through NAT rule panics router. Network input must never panic on adversarial input. Fix: goto oops (IP_FW_DENY) instead of panic.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0571 Β· 14 files
FileTypeDescriptionSize
raw_trigger.c trigger-source raw IP socket SCTP packet injector (root) 2.5 KB view raw
trigger.c trigger-source UDP/multicast IGMP trigger (unpriv attempt) 2.8 KB view raw
build.sh build-script cc -o raw_trigger raw_trigger.c 233 B view raw
run.sh run-script configures NAT rule + runs raw_trigger 445 B view raw
fix.diff suggested-fix replace panic() at lines 219/254 with goto oops 574 B view raw
build.log build-log compiler output of raw_trigger + trigger 242 B view raw
run.log run-log decisive run output 516 B view raw
panic.txt panic-signature fatal panic stack trace from boot.log 543 B view raw
fix_run.log fix-validation-log post-fix run: same PoC, no panic 698 B view raw
env.txt environment uname, cc version, NAT config 390 B view raw
VERDICT.md verdict narrative analysis 4.3 KB ↓ raw
README.md readme human-facing reproduction summary 1.9 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme human-facing reproduction summary
↓ download raw

DF-0571 β€” ipfw3_nat panic on non-TCP/UDP/ICMP packet

Bug

ip_fw3_nat() at sys/net/ipfw3_nat/ip_fw3_nat.c contains two switch statements over ip->ip_p with default cases that call panic("ipfw3: unsupported proto %u", ip->ip_p):

  • inbound switch β€” line 219
  • outbound switch β€” line 254

Any IP packet whose protocol is not TCP (6), UDP (17), or ICMP (1) and that matches an ipfw3 NAT rule will crash the kernel instantly. Common protocols affected: SCTP (132), GRE (47), ESP (50), AH (51), IGMP (2), UDPLite (136).

The outbound switch (line 254) has no NAT/alias gate before it, so any internal host emitting such a protocol through a NAT rule panics the router. Network input paths must never panic on adversarial input.

Setup

  • Requires ipfw3, ipfw3_basic, ipfw3_nat KLDs loaded.
  • Set net.filters_default_to_accept=1 in /boot/loader.conf.local so ssh survives the load (otherwise ipfw3 defaults to deny-all).
  • Configure: ipfw3 nat 1 config ip <ifaddr> and add a NAT rule.

Reproduce

./build.sh
ssh dfbsd /root/setup.sh      # configures NAT rule
./run.sh                       # as root, sends SCTP (proto 132) packet
# kernel panic: panic: ipfw3: unsupported proto 132 at ip_fw3_nat+0x7f

The raw_trigger PoC uses an IPPROTO_RAW socket to inject an IP packet with ip_p = 132 (SCTP). The PFIL hook on IPv4 output routes the packet through ip_fw3_check_out β†’ ip_fw3_chk β†’ check_nat β†’ ip_fw3_nat, which hits default: panic().

Triggering from an unprivileged user is not directly possible on this guest (raw IP sockets require SYSCAP_NONET_RAW). The realistic threat model is a router running NAT: any internal host emitting SCTP/GRE/ESP through a NAT'd interface crashes the router.

Fix

Replace panic(...) at lines 219 and 254 with goto oops; (IP_FW_DENY). The inner switch (state-creation, around line 267-330) already uses goto oops for unknown protocols β€” this fix makes the outer switches consistent. See fix.diff.

VERDICT.md verdict narrative analysis
↓ download raw

DF-0571 β€” Verdict: REPRODUCED (panic / DoS)

Verdict

REPRODUCED. The two panic("ipfw3: unsupported proto %u", ip->ip_p) sites at sys/net/ipfw3_nat/ip_fw3_nat.c:219 (inbound) and :254 (outbound) are both reachable as soon as any ipfw3 NAT rule matches an IP packet whose protocol is not TCP/UDP/ICMP. Reproduced by sending a single IP packet with ip_p = 132 (SCTP) outbound through a NAT'd interface.

Mechanism (trigger β†’ primitive β†’ effect)

  1. ipfw3 nat 1 config ip <ifaddr> β€” install NAT configuration.
  2. ipfw3 add 100 nat 1 ip from any to any out β€” install a NAT rule that matches outbound IPv4 packets.
  3. Any outbound IPv4 packet with proto βˆ‰ {TCP, UDP, ICMP} matches the rule.
  4. PFIL hook ip_fw3_check_out (sys/netinet/ip_output.c:529 β†’ sys/net/ipfw3/ip_fw3.c:1255) calls ip_fw3_chk (sys/net/ipfw3/ ip_fw3.c:318) which dispatches to check_nat (ip_fw3_nat.c:143).
  5. check_nat calls ip_fw3_nat() (ip_fw3_nat.c:173). The outbound branch at line 224 enters switch (ip->ip_p) at line 228.
  6. For proto=132 (SCTP), no case matches; control falls to default: panic("ipfw3: unsupported proto %u", ip->ip_p) at line 254.
  7. Kernel panics. Confirmed stack:

panic: ipfw3: unsupported proto 132 ip_fw3_nat() at ip_fw3_nat+0x7f check_nat() at check_nat+0x60 ip_fw3_chk() at ip_fw3_chk+0x1aa ip_fw3_check_out() at ip_fw3_check_out+0x44 pfil_run_hooks() at pfil_run_hooks+0x6f

PoC

  • raw_trigger.c β€” opens IPPROTO_RAW socket, IP_HDRINCL, builds a minimal IPv4 header with ip_p = 132, sends via sendto. Panic is immediate. Requires SYSCAP_NONET_RAW (root) to open the raw socket.
  • trigger.c β€” alternative unprivileged angle via UDP multicast join; on this guest the kernel-routed IGMP report does not reach the NAT path because the only outbound path it takes is multicast-specific and bypasses ipfw3 NAT for our test setup. (Left as a starting point for unpriv-reachable variants on routers where NAT matches all multicast traffic.)

Threat model / reachability

  • Router scenario (realistic): a DFly router running ipfw3 NAT with a NAT rule that matches outbound traffic. Any host on the NAT'd subnet emitting SCTP/GRE/ESP/AH/IGMP/UDPLite packets crashes the router. The attacker is the sender of the packet, not a local user on the router.
  • Local-user scenario: limited. Unprivileged users on the router host itself can cause panic only if their traffic matches a NAT rule with a non-TCP/UDP/ICMP protocol they can emit. Standard sockets only emit TCP/UDP/ICMP, so this requires either a raw socket (root-only) or a kernel-emitted packet (e.g. IGMP membership report) that matches a NAT rule covering multicast destinations.

Net: DoS (panic) of any ipfw3-NAT-enabled router from any peer that can route an unsupported-protocol IPv4 packet into the NAT'd path. No memory corruption β€” the primitive is the panic itself, which is sufficient for DoS.

PoC changes

  • raw_trigger.c and trigger.c written from scratch (the poc dir was empty when this verification started).

Fix validation (Phase 8)

Built fix.diff (replaces panic(...) at lines 219 and 254 with goto oops;, matching the existing inner-switch behavior at line 328). Built the single-module change in /usr/src/sys/net/ipfw3_nat with make, installed the rebuilt ipfw3_nat.ko to /boot/kernel/, rebooted.

  • Baseline (#0, unpatched module): PoC panics instantly: panic: ipfw3: unsupported proto 132 at ip_fw3_nat+0x7f.
  • Patched (rebuilt module): same PoC returns cleanly. NAT rule counter still increments (proving the packet matched and went through check_nat), but instead of panicking the kernel returns IP_FW_DENY via goto oops, and sendto returns EACCES. Guest stays up.

The fix is consistent with the inner switch's existing handling (default: goto oops; at line 328) β€” this just brings the two outer switches in line with that.

Kernel references

Fix verification

fixed

validated

see evidence pack
↓ fix.diffn/a (module-level)

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

REPRODUCED (live panic). ipfw3_nat panic on non-TCP/UDP/ICMP packet. Root-only raw socket. Module fix: goto oops.