core: no cap on layer nesting — 64 KB packet decodes to 16,002 layers, and Dump() turns it into 45 MB and 68 ms #12
Labels
No labels
core
cpu-dos
critical
dos
evasion
has-poc
high
integer-overflow
ip4defrag
layers
low
medium
memory-exhaustion
other
panic
pcapgo
pentest-2026-08
rce
tcpassembly
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
noi/gopacket#12
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: medium ·
packet.go(packetLayers,String,Dump),layers/mpls.go,layers/ip6.gogopacket places no bound on how many layers a single packet may decode into. Any self-referencing header chain — stacked MPLS labels, IPv6 extension headers, nested tunnels — becomes as many layers as the attacker can fit in one packet, and the per-layer formatting paths then amplify that by another two to three orders of magnitude.
Reproduction
Both packets are entirely well-formed. The MPLS one is a 64 KB frame carrying 16,000 four-byte labels with the bottom-of-stack bit set only on the last; the IPv6 one is 8,000 minimum-size hop-by-hop headers. Neither exceeds any limit gopacket enforces, because there are none:
layers/mpls.godecodes a label and, if the bottom-of-stack bit is clear, hands the remainder straight back toLayerTypeMPLS. No depth counter.layers/ip6.gowalks the next-header chain to its end with no bound on the number of extension headers.packet.goappends top.layerswithout a ceiling.Impact, honestly split
Decode alone is not the problem. 28–36× allocation amplification and ~5.5 ms per 64 KB packet is bad but survivable — about 90 Mbit/s to saturate a core.
The formatting paths are.
String()is ~494× andDump()is ~744×:Dump()on one 64 KB packet: 45.5 MB allocated, 68 ms of CPU, 3.2 MB of text.This matters because
String()andDump()are exactly what a monitoring tool calls on the packets an operator is looking at: a packet-detail view, a debug log line, a "why did this not parse" diagnostic. The attacker picks which packet the operator clicks on by making it interesting. It is the same shape as daisy's finding 12 (one flow hanging the browser), one layer down.There is a second-order effect worth noting: 16,002 layers means 16,002 entries in
p.layers, and any consumer that iterates layers per packet — a layer-type histogram, a tag matcher, a protocol-hierarchy stat — inherits the same multiplier without ever calling a formatter.Fix
In gopacket, a decode-depth ceiling in
packet.go, enforced where layers are appended rather than in each decoder:128 is well above anything legitimate — real stacks cap MPLS depth in the low tens and Linux caps the IPv6 extension-header chain far below that.
In callers, treat
String()andDump()as unsafe on untrusted packets: cap the layer count before formatting, or cap the output length. A monitoring UI should never hand a rawDump()of an attacker-supplied packet to a renderer.Verified against
b7d9dbdon Go 1.24.4. PoC:ip6. Related: daisy finding 12 — same amplification pattern reaching the operator's browser.