gopacket: SCTP chunk sub-decoder out-of-bounds panics on crafted segmentsGHSA-358w-w75h-x6rx
MEDIUMFix: gopacket/gopacket@5080443GHSA-358w-w75h-x6rx is a medium-severity (CVSS 5.9) Out-of-bounds Read vulnerability in github.com/gopacket/gopacket. A fix is available for github.com/gopacket/gopacket — see the affected versions and patch details below.
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
Exploitation and automatability from CISA’s SSVC triage for GHSA-358w-w75h-x6rx.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
Where this sits among everything scored
Real-World Exposure
github.com/gopacket/gopacketReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
Description
Summary
gopacket decodes SCTP (IP protocol 132) by walking the chunk chain through per-chunk-type decoder functions (decodeSCTPInit, decodeSCTPShutdown, decodeSCTPError, decodeSCTPSack, in layers/sctp.go). Each of these reads fixed-size header fields and runs count-driven loops without validating the chunk's declared length against the minimums those reads require. A single crafted SCTP segment causes a runtime panic (out-of-bounds slice read, or inverted slice from unsigned length arithmetic) on any decode path that does not install gopacket's per-packet panic recovery (gopacket.DecodeOptions.SkipDecodeRecovery: true); with the default API the same input degrades to a per-packet decode error.
The defect is present in every released version through v1.7.1 and at current master (18c5805). It is the SCTP component named - but not remediated - in CVE-2026-65819 (GHSA-8mcr-459q-5mx2, "Multiple layer decoders panic …", patched per advisory metadata in v1.7.1). The v1.7.1 hardening commit b772f56 ("layers:various: add protocol decoder length checks (#163)") added generic bounds to the chunk dispatcher and decodeSCTPChunk, but left every chunk sub-decoder without a per-type minimum-length check. The advisory's own description lists "SCTP chunk/parameter decoders (sctp.go)" among items "being confirmed; they should be remediated together with the above" - this report demonstrates that, at v1.7.1 and HEAD, that remediation still does not exist.
Patches
Fixed in v1.7.2 by commit 5080443 ("layers/sctp: bound chunk sub-decoders and parameters"). INIT/INIT-ACK, SACK and SHUTDOWN now check their chunk type's minimum length, SACK rejects gap-ack and duplicate-TSN counts the chunk can't hold, and decodeSCTPParameter rejects TLV lengths below 4 or beyond the buffer. Each case returns a decode error instead of panicking. The same commit fixes a panic on valid input, where a final parameter's padding lies outside the chunk length (RFC 9260 3.2). Regression tests: TestDecodeSCTPChunkRegression and TestDecodeSCTPParameterPadding in layers/decode_oob_test.go.
Upgrade with go get github.com/gopacket/[email protected].
Workarounds
Before v1.7.2, keep panic recovery on: use gopacket.NewPacket without SkipDecodeRecovery, and DecodingLayerParser with IgnorePanic: false. The crafted packet then fails to decode instead of crashing the process.
Vulnerability Details
All line numbers refer to layers/sctp.go at commit 18c5805028329a6a8944f054d8d3f64ebc0f1373 (master, 2026-09-01; v1.7.1-1-g18c5805).
The shared entry point already present in v1.7.1 validates only that a chunk is present and that its declared length is ≥ 4 and covered by real bytes:
// sctp.go:129
func decodeSCTPChunk(data []byte) (SCTPChunk, error) {
if len(data) < 4 { ... }
length := binary.BigEndian.Uint16(data[2:4])
if length < 4 { ... }
actual := roundUpToNearest4(int(length))
ct := SCTPChunkType(data[0])
if ct == SCTPChunkTypeData && length < 16 { ... } // only DATA gets a 16-byte minimum
if len(data) < actual { ... }
...
The Data-chunk minimum of 16 bytes is never applied to the other chunk types, and none of the sub-decoders re-check before indexing. Four independent panic paths result (each reproduced at HEAD):
Defect 1 - INIT / INIT-ACK chunks (decodeSCTPInit, sctp.go:423): reads data[4:8], data[8:12] (line 431), data[12:14], data[14:16], data[16:20] (line 434), then slices paramData := data[20:sc.ActualLength] (line 436).
- Declared length 8–11, actual 8–12 ->
data[8:12]reads past the chunk:panic: runtime error: slice bounds out of range [:12] with capacity 8 - Declared length 15, actual 16 ->
data[16:20]past the chunk:panic: runtime error: slice bounds out of range [:20] with capacity 16 - Declared length ≤ 19 -> the
data[20:ActualLength]slice is inverted (panic) or the subsequent parameter loop runs on attacker data without thedecodeSCTPParameterlength check (Defect 3).
Defect 2 - SHUTDOWN chunks (decodeSCTPShutdown, sctp.go:670): reads binary.BigEndian.Uint32(data[4:8]) (line 677) on a chunk whose declared length is only validated as ≥ 4:
panic: runtime error: slice bounds out of range [:8] with capacity 4 (4-byte chunk).
Defect 3 - ERROR (and HEARTBEAT/INIT) parameter decoder (decodeSCTPParameter, sctp.go:193): the crafted Length field is used to slice with no bound against the parameter buffer:
// sctp.go:193
func decodeSCTPParameter(data []byte) SCTPParameter {
length := binary.BigEndian.Uint16(data[2:4])
return SCTPParameter{
...
Value: data[4:length], // line 198: no check length >= 4 or length <= len(data)
ActualLength: roundUpToNearest4(int(length)),
}
}
length < 4-> inverted slice:panic: runtime error: slice bounds out of range [4:0](reached fromdecodeSCTPErrorline 632paramData := data[4:sc.Length], and from the INIT (438) / HEARTBEAT (578) parameter loops).length > len(data)-> slice-bounds-check panic (Go's slice bounds check panics; the read never silently over-reads).
Defect 4 - SACK chunk count loops (decodeSCTPSack, sctp.go:484): NumGapACKs / NumDuplicateTSNs (attacker-controlled uint16 fields) drive loops that slice the remaining bytes with no bound:
// sctp.go:512
bytesRemaining := data[16:]
for i := 0; i < int(sc.NumGapACKs); i++ { // line 513
sc.GapACKs = append(sc.GapACKs, binary.BigEndian.Uint16(bytesRemaining[:2])) // line 514
bytesRemaining = bytesRemaining[2:]
}
for i := 0; i < int(sc.NumDuplicateTSNs); i++ { // line 517
sc.DuplicateTSNs = append(sc.DuplicateTSNs, binary.BigEndian.Uint32(bytesRemaining[:4])) // line 518
bytesRemaining = bytesRemaining[4:]
}
A 16-byte SACK chunk declaring NumGapACKs = 1 with no following bytes:
panic: runtime error: slice bounds out of range [:2] with capacity 0
Note this is not the sFlow/Diameter "unbounded allocation" family already covered by CVE-2026-54332/CVE-2026-54345; the SACK make() capacities are already capped against ActualLength (lines 505–510) - the loop iteration counts are not, so the panic is an out-of-bounds read, not an allocation.
History / version range. decodeSCTPInit and the chunk sub-decoders were introduced in commit 2fca351 (original full-featured SCTP implementation; at the time the file was named layer_sctp.go - the current sctp.go path dates from the rename in dea6b39). The sinks are present in every release from v1.0.0 through v1.7.1 and at HEAD (also confirmed at v1.6.0). b772f56 (2026-07-08, included in v1.7.1) touched layers/sctp.go but only hardened the dispatcher (decodeWithSCTPChunkTypePrefix) and the generic decodeSCTPChunk/decodeSCTPDataChunk bounds; the four sub-decoder paths above are unchanged by it. The v1.7.1 regression-test commit 2220269 covers the advisory's other decoders, not SCTP chunk sub-decoders. Affected range is therefore every gopacket version: <= 1.7.1, plus current master 18c5805. (Maintainer update: fixed in v1.7.2, commit 5080443; see Patches.)
Relationship to the published advisory (dedup context). CVE-2026-65819 / GHSA-8mcr-459q-5mx2 (published 2026-07-27) declares vulnerable_version_range: <= 1.7.0 / patched_versions: 1.7.1. Its description explicitly names "SCTP chunk/parameter decoders (sctp.go)" among decoder families "(on the recovering path only) … being confirmed; they should be remediated together with the above". Because the advisory metadata tells consumers that upgrading to 1.7.1 remediates the advisory, and this SCTP component was not actually fixed, the practical status of this finding is incomplete remediation of a published advisory: consumers of the 1.7.1 "patched" version remain exposed to the SCTP panic path. The finding is reported here so the maintainers can either complete the SCTP fix under the existing advisory or track it separately.
Impact
An unauthenticated remote attacker can crash a gopacket-based decoder with a single crafted SCTP segment (IP protocol 132). A 54-byte Ethernet frame (Ethernet -> IPv4 -> SCTP with an 8-byte INIT chunk) is enough (see Reproduction Steps). Panic escapes for consumers that disable gopacket's built-in recovery:
gopacket.NewPacket(data, layerType, DecodeOptions{SkipDecodeRecovery: true})- panic propagates to the caller, terminating the process if uncaught.- Direct use of the registered SCTP decode chain without recovery.
Two gopacket entry points install recovery by default and are therefore NOT crash surfaces for this bug: gopacket.NewPacket with default options (deferred recover wraps the whole decode chain, converting the panic into a per-packet error, demonstrated in case B below), and DecodingLayerParser.DecodeLayers when IgnorePanic is false (parser.go:306 - if !l.IgnorePanic { defer panicToError(&err) }); in both cases the packet simply fails to decode. Panic escape - and process crash - requires the non-recovering settings above. This mirrors exactly the reachability of the sibling advisories in the same family (GHSA-6h9g MPTCP, GHSA-8mcr): CVE-2026-65819 itself is scored with the same AV:N/AC:L/PR:N/UI:N vector while noting (per the GHSA advisory text) ~5.3 (Medium) for recovering-path-only consumers; the reporter scored the non-recovering consumer set at 7.5, which is the configuration used by high-throughput packet-analysis engines that set SkipDecodeRecovery to detect decode failures themselves.
The default API (gopacket.NewPacket(..., gopacket.Default)) installs defer recover and converts the panic into a per-packet decode error, so crash exposure is limited to non-recovering consumers (packet-analysis engines, IDS/NDR pipelines, capture-processing tools that set SkipDecodeRecovery or call decoders directly - the same consumer set described in the CVE-2026-65819 and CVE-2026-54332/54345 advisories).
Impact is availability only: no memory disclosure, no code execution, no allocation amplification. Severity: CVSS 3.1 5.9 Medium - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H (reporter proposed 7.5 with AC:L; maintainers set AC:H because a crash needs the consumer to disable panic recovery), primary CWE-125 (out-of-bounds read), secondaries CWE-129 (improper validation of array index for the inverted slices).
Per-metric justification (primary vector):
- Attack Vector (AV): Network - a single crafted SCTP segment (IP protocol 132) delivered over the wire triggers the panic path; no local access needed.
- Attack Complexity (AC): High - the input is trivial (a 54-byte frame with an 8-byte INIT chunk), but a crash depends on a condition outside the attacker's control: the target must have disabled gopacket's panic recovery. Default configurations only produce a decode error.
- Privileges Required (PR): None - the decoding library processes untrusted captured/live packets with no authentication.
- User Interaction (UI): None - no victim action; the crash occurs during automated decode.
- Scope (S): Unchanged - the impacted security authority is the decoder's own process (the caller's application).
- Confidentiality (C): None - no data disclosure.
- Integrity (I): None - no data modification.
- Availability (A): High - non-recovering consumer process terminates (panic); the ~5.3 recovering-path-only alternative is disclosed above.
Reproduction Steps
Environment: Go 1.25 toolchain (module requires go 1.25.0), github.com/gopacket/gopacket at HEAD 18c5805028329a6a8944f054d8d3f64ebc0f1373 (v1.7.1 + 1 commit). The same output is produced at tag v1.7.1.
// repro_sctp.go - single static input, one deterministic observable (decode panic)
package main
import (
"fmt"
"github.com/gopacket/gopacket"
"github.com/gopacket/gopacket/layers"
)
func main() {
// One 54-byte frame: Ethernet -> IPv4(proto 132) -> SCTP -> INIT chunk (declared length 8)
frame := []byte{
// Ethernet
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x08, 0x00,
// IPv4, protocol 132 (SCTP), total length 40
0x45, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x84,
0x0a, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x00, 0x02,
// SCTP common header (12 bytes)
0x11, 0x11, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// INIT chunk: type=1, flags=0, length=8 (declared), body 4 bytes
0x01, 0x00, 0x00, 0x08, 0xaa, 0xbb, 0xcc, 0xdd,
}
// same bytes as hex:
// ffffffffffff00112233445508004500002800000000008400840a0000010a000002
// 1111222200000000000000000100000008aabbccdd
// A) Non-recovering decode (SkipDecodeRecovery: true) -> panic escapes
func() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("A) SkipDecodeRecovery=true -> PANIC: %v\n", r)
}
}()
p := gopacket.NewPacket(frame, layers.LayerTypeEthernet,
gopacket.DecodeOptions{Lazy: false, SkipDecodeRecovery: true})
_ = p.Dump()
fmt.Println("A) no panic (unexpected)")
}()
// B) Default (recovering) API -> per-packet error, process survives
p := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default)
fmt.Printf("B) Default (recovering) -> err=%v layers=%d\n", p.ErrorLayer(), len(p.Layers()))
}
Build and run (module file go.mod):
module repro
go 1.25.0
require github.com/gopacket/gopacket v1.7.1
replace github.com/gopacket/gopacket => /path/to/clone/of/gopacket/gopacket # at 18c5805 (or tag v1.7.1)
$ go build -o repro_sctp repro_sctp.go
$ ./repro_sctp
A) SkipDecodeRecovery=true -> PANIC: runtime error: slice bounds out of range [:12] with capacity 8
B) Default (recovering) -> err=Packet decoding error: runtime error: slice bounds out of range [:12] with capacity 8 layers=4
Observed stack (case A): layers.decodeSCTPInit at layers/sctp.go:431 <- SCTPChunkType.Decode <- decodeWithSCTPChunkTypePrefix (sctp.go:57) <- decodeSCTP (sctp.go:38) <- NewPacket.
Additional one-input repros for the other three defect classes (same harness shape, decode root LayerTypeSCTP with SkipDecodeRecovery: true):
| Input | Panic (observed) | Sink |
|---|---|---|
| INIT-ACK chunk type 2, declared length 8 | slice bounds out of range [:12] with capacity 8 | sctp.go:431 |
| INIT chunk declared length 15, 12-byte body | slice bounds out of range [:20] with capacity 16 | sctp.go:434 |
SACK chunk type 3, length 16, NumGapACKs=1, NumDuplicateTSNs=1, no trailing bytes | slice bounds out of range [:2] with capacity 0 | sctp.go:514 |
| SHUTDOWN chunk type 7, declared length 4 | slice bounds out of range [:8] with capacity 4 | sctp.go:677 |
| ERROR chunk type 9, length 8, 4-byte "parameter" with Length field 0 | slice bounds out of range [4:0] | sctp.go:198 |
Recommendations
Complete the v1.7.1 remediation by validating each chunk subtype's declared length against the fixed-size fields and loops that consume it, immediately after decodeSCTPChunk succeeds in each sub-decoder. Minimal correct changes in layers/sctp.go:
decodeSCTPInit: requiresc.ActualLength >= 20before readingdata[4:20](return a decode error otherwise), then bound the parameter loop:paramData := data[20:sc.ActualLength]is safe once the chunk length is ≥ 20 anddecodeSCTPParameteris hardened (below).decodeSCTPShutdown: requiresc.ActualLength >= 8.decodeSCTPSack: requiresc.ActualLength >= 16, and cap the loops - iteratemin(NumGapACKs, remaining/2)andmin(NumDuplicateTSNs, remaining/4), returning an error (or silently stopping withSetTruncated) when a count exceeds the bytes available.decodeSCTPParameter(shared by INIT/HEARTBEAT/ERROR): rejectlength < 4andlength > len(data)before thedata[4:length]slice, mirroring the bound style already used for TCP options (tcp.godefaultbranch) - this closes Defect 3 at its root regardless of which loop calls it.
Optionally add regression tests in the style of 2220269 covering short INIT/SHUTDOWN/SACK/ERROR chunks.
Resources
- Source at pin:
layers/sctp.go@ https://github.com/gopacket/gopacket/blob/18c5805028329a6a8944f054d8d3f64ebc0f1373/layers/sctp.go (lines 129–136 dispatcher/chunk guard, 193–202decodeSCTPParameter, 423–442decodeSCTPInit, 484–521decodeSCTPSack, 670–678decodeSCTPShutdown, 623–636decodeSCTPError) - Fix commit that missed the sub-decoders: https://github.com/gopacket/gopacket/commit/b772f566a3ecc53c099bdd44eab4718088c4e866 (PR #163, "layers:various: add protocol decoder length checks")
- Regression-test commit: https://github.com/gopacket/gopacket/commit/2220269 (v1.7.1, "test(layers): add regression tests for the decoder panic advisories")
- Advisory that names the unfixed SCTP component: https://github.com/gopacket/gopacket/security/advisories/GHSA-8mcr-459q-5mx2 (CVE-2026-65819, "Multiple layer decoders panic on crafted packets …, including (on the recovering path only) … SCTP chunk/parameter decoders (sctp.go)"); NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-65819
- Sibling advisory with identical reachability framing (High 7.5): https://github.com/gopacket/gopacket/security/advisories/GHSA-6h9g-cjv3-pg2c (TCP MPTCP option decoder)
- Introduced in: https://github.com/gopacket/gopacket/commit/2fca351 (original SCTP implementation)
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | github.com/gopacket/gopacket | all versions | 1.7.2go get github.com/gopacket/gopacket@v1.7.2 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/gopacket/gopacket, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update github.com/gopacket/gopacket to 1.7.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-358w-w75h-x6rx is resolved across your whole dependency graph.
Workarounds
Stop feeding it untrusted input: reject or quarantine files and payloads from unverified sources until you can upgrade, restrict accepted formats to the ones you actually need, and run the parsing or decoding step in a least-privileged sandbox or short-lived worker so a crash or corrupted read cannot reach the rest of the process.
Fixing This On Your OS
If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.
This vulnerability is rated as having a Moderate impact because exploitation is limited to a denial of service and requires specific parser configurations to cause an unhandled process termination. In typical Red Hat deployments, gopacket's standard packet decoding configuration handles panics gracefully, turning…
No known mitigations are currently available for this vulnerability. We recommend applying the security updates once they are released.Source: Red Hat security advisory for GHSA-358w-w75h-x6rx (CC BY 4.0)
Frequently Asked Questions
Is GHSA-358w-w75h-x6rx in your dependencies?
Find it across Go, including transitive dependencies.