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)
PoC verification
Evidence pack
findings/poc/DF-0571 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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_natKLDs loaded. - Set
net.filters_default_to_accept=1in/boot/loader.conf.localso 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.
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)
ipfw3 nat 1 config ip <ifaddr>β install NAT configuration.ipfw3 add 100 nat 1 ip from any to any outβ install a NAT rule that matches outbound IPv4 packets.- Any outbound IPv4 packet with proto β {TCP, UDP, ICMP} matches the rule.
- PFIL hook
ip_fw3_check_out(sys/netinet/ip_output.c:529 β sys/net/ipfw3/ip_fw3.c:1255) callsip_fw3_chk(sys/net/ipfw3/ ip_fw3.c:318) which dispatches tocheck_nat(ip_fw3_nat.c:143). check_natcallsip_fw3_nat()(ip_fw3_nat.c:173). The outbound branch at line 224 entersswitch (ip->ip_p)at line 228.- For proto=132 (SCTP), no case matches; control falls to
default: panic("ipfw3: unsupported proto %u", ip->ip_p)at line 254. - 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β opensIPPROTO_RAWsocket, IP_HDRINCL, builds a minimal IPv4 header withip_p = 132, sends viasendto. Panic is immediate. RequiresSYSCAP_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.candtrigger.cwritten 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 132atip_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
- sys/net/ipfw3_nat/ip_fw3_nat.c:219 β inbound panic site (confirmed)
- sys/net/ipfw3_nat/ip_fw3_nat.c:254 β outbound panic site (confirmed)
- sys/net/ipfw3_nat/ip_fw3_nat.c:328 β existing
goto oopsfor same condition in the inner switch (precedent for the fix) - sys/net/ipfw3_nat/ip_fw3_nat.c:430 β
oops:label returns IP_FW_DENY - sys/netinet/ip_output.c:529 β PFIL hook that drives the path
Fix verification
fixedvalidated
see evidence pack
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.
No comments yet.