GHSA-g6v3-7xmc-w563 GoPacket's sFlow ExtendedGatewayFlow decoder: unbounded attacker-controlled allocation (104-byte UDP datagram -> up to 16 GiB make) -> unauthenticated remote DoS
SummaryThe sFlow `ExtendedGatewayFlow` record decoder in `github.com/gopacket/gopacket` allocates a slice with `make([]uint32, n)` where `n` is an attacker-controlled 32-bit wire field that has no upper bound. Because the allocation happens *before* the read loop that would consume the corresponding bytes, a single small UDP datagram can force a multi-gigabyte allocation. A 104-byte sFlow datagram can request up to 16 GiB and OOM-kill any service that parses sFlow with gopacket. This is an unauthenticated remote denial of service (CWE-770).Root cause (file:line @ v1.6.0)Two sinks in `layers/sflow.go`, both in the `ExtendedGatewayFlow` (record type 1003) decode path:`layers/sflow.go:1306` in `decodeExtendedGatewayFlowRecord`:`layers/sflow.go:1276` in `decodePath` (a helper called from the same record decoder):In both cases the `make` is executed before the loop that reads the element bytes, so the allocation size is fully determined by the attacker-supplied count field and is never checked against the number of bytes actually remaining in the packet. `communitiesLength = 0xFFFFFFFF` requests `make([]uint32, 4294967295)` = 16,384 MB (16 GiB).Reachability (remote attacker -> sink)The registered `LayerTypeSFlow` decoder parses sFlow datagrams from the wire:`SFlowDatagram.DecodeFromBytes` (sflow.go:302) -> `SampleCount` loop -> `decodeFlowSample(expanded=false)` (sflow.go:458) -> `RecordCount` loop -> record format 1003 `SFlowTypeExtendedGatewayFlow` (sflow.go:573) -> `decodeExtendedGatewayFlowRecord` (sflow.go:1284) -> sink at line 1306 (and line 1276 via the `ASPath` -> `decodePath` branch).sFlow is a UDP-based network-telemetry protocol; collectors built on gopacket process datagrams sent (or forwarded by switches/routers) from the network. No authentication is involved, so any host that can deliver a UDP packet to such a collector can trigger the sink. The same record reached via `gopacket.NewPacket(data, LayerTypeSFlow, gopacket.Default)` is equally affected.ImpactUnauthenticated remote denial of service via memory exhaustion. A single 104-byte datagram drives an allocation of up to 16 GiB, OOM-killing the parsing process. There is no memory corruption and no code execution — the impact is process termination / resource exhaustion. Severity assessed as Medium (unauthenticated remote DoS, no memory-safety violation).Proof of ConceptThis PoC is an end-to-end test against a real deployed sFlow collector. A minimal
but realistic UDP collector (built on the public gopacket API, exactly as a real
network-telemetry collector would be) runs inside a hard-capped 256 MB container;
an independent client process sends a real malicious sFlow datagram over a real
UDP socket; the collector process is then observed to die. A benign datagram is
used as a negative control.The harness pins `github.com/gopacket/gopacket@v1.6.0` (the sink is confirmed at
the v1.6.0 tag, `layers/sflow.go:1306`).Collector (real UDP sFlow collector)Client (independent process, real UDP socket, no gopacket dependency)The client crafts a well-formed sFlow v5 datagram with one FlowSample carrying
one ExtendedGatewayFlow (record type 1003) record and sets only its
`communitiesLength` field. For the benign case it appends real community words
plus the trailing LocalPref word so the record decodes cleanly.Run and observed resultThe collector runs under a hard 256 MB cgroup cap with swap disabled
(`--memory=256m --memory-swap=256m`) so the OOM is contained to the cgroup and
the host is unaffected.Negative control (benign datagram, `communitiesLength=4`):Attack (single malicious datagram, `communitiesLength=0xFFFFFFFF`):A single 104-byte UDP datagram, delivered over a real socket to a real
gopacket-based collector, terminates the collector process. The Go runtime tries
to `sysMapOS` 0x400000000 (16 GiB) into the 256 MB cgroup, the mapping is denied,
and the runtime aborts with `fatal error: runtime: out of memory` (exit 2). The
full attacker -> sink call stack is captured: `collector.go:54`
(`gopacket.NewPacket`) -> `SFlowDatagram.DecodeFromBytes` -> `decodeFlowSample`
(sflow.go:574) -> `decodeExtendedGatewayFlowRecord` (sflow.go:1306) ->
`makeslice` -> fatal OOM. The benign control on the same collector decodes
cleanly and the process stays alive with flat RSS, confirming the
attacker-controlled `communitiesLength` field is what drives the allocation.The host is unaffected throughout: the allocation is contained by the 256 MB
cgroup cap (no swap), and host swap stayed above 900 MB free across the run.Affected versions`github.com/gopacket/gopacket` <= v1.6.0 (v1.6.0 is the latest release; HEAD == tag). Earlier versions carrying the same `layers/sflow.go` decode code are affected as well.Suggested fixBefore each `make([]uint32, n)`, validate `n` against the number of bytes actually remaining in the datagram. Each element consumes 4 bytes on the wire, so a correct upper bound is `remaining_bytes / 4`; any count larger than that cannot be backed by real packet data and should be rejected with a decode error (matching the existing `errors.New` / `fmt.Errorf` error style in this file), rather than pre-allocating. This caps the allocation at roughly the datagram size and eliminates the amplification:`decodeExtendedGatewayFlowRecord`: reject when `communitiesLength > uint32(len(*data)/4)` before `make([]uint32, communitiesLength)`.`decodePath`: reject when `ad.Count > uint32(len(*data)/4)` before `make([]uint32, ad.Count)`, and propagate the error to the caller.I will follow up with a fix PR via the advisory's private fork.ReferencessFlow Version 5 specification (https://sflow.org/sflow_version_5.txt), section on the extended_gateway flow_data record (communities / dst_as_path lists).CWE-770: Allocation of Resources Without Limits or Throttling.
CVSS v4.0 score: 6.9