GHSA-JWJP-4649-V8JP
Vulnerability from github – Published: 2026-08-12 19:30 – Updated: 2026-08-12 19:30Summary
SctpSackChunk.ParseChunk reads the numGapAckBlocks and numDuplicateTSNs fields (each up to 65535) directly from an attacker-controlled SCTP SACK chunk and loops that many times reading 4 bytes per iteration, with no validation of the counts against the chunk length or the receive buffer. A single crafted SACK chunk from a negotiated WebRTC peer forces reads past the end of the 262144-byte receive buffer, raising IndexOutOfRangeException, which is not caught by the recoverable handler and terminates the dedicated SCTP receive thread — permanently killing the SCTP association and all data channels.
Root Cause
src/SIPSorcery/net/SCTP/Chunks/SctpSackChunk.cs:
- ushort numGapAckBlocks = NetConvert.ParseUInt16(buffer, startPosn + 8); (:141)
- ushort numDuplicateTSNs = NetConvert.ParseUInt16(buffer, startPosn + 10); (:142)
- gap-ack loop (:146) and duplicate-TSN loop (:154) index the buffer via NetConvert.ParseUInt16/32 (buffer[posn], no bounds check — sys/Net/NetConvert.cs:30,41).
SctpPacket.ParseChunks (SctpPacket.cs:195-203) only validates chunkLength >= 4 and posn+chunkLength <= length; the counts inside the value are never checked. RTCSctpTransport.DoReceive calls SctpPacket.Parse(recvBuffer, 0, bytesRead) on a reused recvBuffer = new byte[262144].
Impact
IndexOutOfRangeException is a SystemException, not ApplicationException, so the recoverable catch (ApplicationException) { … continue; } at RTCSctpTransport.cs:345 is skipped and control falls to the generic catch (Exception) { … break; } at :356. The break exits the receive loop, DoReceive returns, and the dedicated _receiveThread = new Thread(DoReceive) (:173, started once) exits with no restart → the SCTP association and every data channel are permanently dead (denial of service).
Proof of Concept
A negotiated WebRTC peer (post-DTLS) sends a checksum-valid SCTP packet: 12-byte common header + a SACK chunk (type 3) with chunkLength=16, numGapAckBlocks=0xFFFF, numDuplicateTSNs=0xFFFF. CRC32C is attacker-computable. The gap-ack loop reaches buffer[262144] on a 262144-byte array (valid indices 0..262143) → IndexOutOfRangeException.
Attack Chain
- Entry: post-DTLS negotiated peer sends a checksum-valid SCTP packet with a SACK chunk (
chunkLength=16,numGapAckBlocks=0xFFFF). Guard:VerifyChecksum(CRC32C). Bypass: CRC32C is computable by the sender. - Processing:
DoReceive(RTCSctpTransport.cs:286) reads into reusedrecvBuffer(262144 bytes, :280) →SctpPacket.Parse(recvBuffer, 0, bytesRead)(:302) →ParseChunks→ SACK dispatch (SctpChunk.Parse:340-341) →SctpSackChunk.ParseChunk. Guard:ParseChunkschecks onlychunkLength>=4andposn+chunkLength<=length(SctpPacket.cs:195-203). Bypass:chunkLength=16is well-formed; the counts are never validated. - Sink: gap-ack loop (SctpSackChunk.cs:146) calls
NetConvert.ParseUInt16(buffer, reportPosn)withreportPosnstarting atstartPosn(16)+FIXED_PARAMETERS(12)=28, climbing+4each iteration. Guard: none on the count. Bypass:NetConvert.ParseUInt16(NetConvert.cs:30) indexesbuffer[posn]unchecked. - Impact: at iteration 65529,
reportPosn = 28 + 65529*4 = 262144→buffer[262144]→IndexOutOfRangeException→ genericcatchat RTCSctpTransport.cs:356 →break→ receive thread exits, no restart → association permanently dead.
Bypass Evidence
- Unchecked counts at SctpSackChunk.cs:141-142; loops at :146,:154.
NetConvert.ParseUInt16unchecked indexing (NetConvert.cs:30).ParseChunksvalidates onlychunkLength(SctpPacket.cs:195-203).- OOB math:
28 + 65535*4 = 262168 > 262144; buffer is 262144 (DEFAULT_ADVERTISED_RECEIVE_WINDOW, SctpAssociation.cs:62).numGapAckBlocksalone suffices — the dup-TSN loop is not needed. DoReceivecatch split: recoverablecatch(ApplicationException)at :345 (continue) vs genericcatch(Exception)at :356 (break);_receiveThreadstarted once at :176.
Affected Versions
nuget:SIPSorcery <= 10.0.13 (verified present on release tag v10.0.13 and HEAD da944543).
Dedup
NOT a duplicate of GHSA-qmvg-569h-hqrh — that fix (fe5a1fa) touched only SctpPacket.cs (the chunk-cursor zero-length infinite loop, CWE-835). This is a distinct out-of-bounds read (CWE-125) in SctpSackChunk count loops, untouched by that fix.
Suggested Fix
Validate startPosn + FIXED_PARAMETERS_LENGTH + numGapAckBlocks*4 + numDuplicateTSNs*4 <= posn + chunkLen before the loops, and/or make NetConvert.Parse* bounds-checked, and/or treat IndexOutOfRangeException/ArgumentException as recoverable in DoReceive.
Reported by zx (Jace) — GitHub: @manus-use
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 10.0.13"
},
"package": {
"ecosystem": "NuGet",
"name": "SIPSorcery"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "10.0.14"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-125",
"CWE-755"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-12T19:30:43Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n`SctpSackChunk.ParseChunk` reads the `numGapAckBlocks` and `numDuplicateTSNs` fields (each up to 65535) directly from an attacker-controlled SCTP SACK chunk and loops that many times reading 4 bytes per iteration, with no validation of the counts against the chunk length or the receive buffer. A single crafted SACK chunk from a negotiated WebRTC peer forces reads past the end of the 262144-byte receive buffer, raising `IndexOutOfRangeException`, which is not caught by the recoverable handler and terminates the dedicated SCTP receive thread \u2014 permanently killing the SCTP association and all data channels.\n\n## Root Cause\n`src/SIPSorcery/net/SCTP/Chunks/SctpSackChunk.cs`:\n- `ushort numGapAckBlocks = NetConvert.ParseUInt16(buffer, startPosn + 8);` (:141)\n- `ushort numDuplicateTSNs = NetConvert.ParseUInt16(buffer, startPosn + 10);` (:142)\n- gap-ack loop (:146) and duplicate-TSN loop (:154) index the buffer via `NetConvert.ParseUInt16/32` (`buffer[posn]`, no bounds check \u2014 `sys/Net/NetConvert.cs:30,41`).\n`SctpPacket.ParseChunks` (SctpPacket.cs:195-203) only validates `chunkLength \u003e= 4` and `posn+chunkLength \u003c= length`; the counts inside the value are never checked. `RTCSctpTransport.DoReceive` calls `SctpPacket.Parse(recvBuffer, 0, bytesRead)` on a reused `recvBuffer = new byte[262144]`.\n\n## Impact\n`IndexOutOfRangeException` is a `SystemException`, not `ApplicationException`, so the recoverable `catch (ApplicationException) { \u2026 continue; }` at RTCSctpTransport.cs:345 is skipped and control falls to the generic `catch (Exception) { \u2026 break; }` at :356. The `break` exits the receive loop, `DoReceive` returns, and the dedicated `_receiveThread = new Thread(DoReceive)` (:173, started once) exits with no restart \u2192 the SCTP association and every data channel are permanently dead (denial of service).\n\n## Proof of Concept\nA negotiated WebRTC peer (post-DTLS) sends a checksum-valid SCTP packet: 12-byte common header + a SACK chunk (type 3) with `chunkLength=16`, `numGapAckBlocks=0xFFFF`, `numDuplicateTSNs=0xFFFF`. CRC32C is attacker-computable. The gap-ack loop reaches `buffer[262144]` on a 262144-byte array (valid indices 0..262143) \u2192 `IndexOutOfRangeException`.\n\n## Attack Chain\n1. Entry: post-DTLS negotiated peer sends a checksum-valid SCTP packet with a SACK chunk (`chunkLength=16`, `numGapAckBlocks=0xFFFF`). Guard: `VerifyChecksum` (CRC32C). Bypass: CRC32C is computable by the sender.\n2. Processing: `DoReceive` (RTCSctpTransport.cs:286) reads into reused `recvBuffer` (262144 bytes, :280) \u2192 `SctpPacket.Parse(recvBuffer, 0, bytesRead)` (:302) \u2192 `ParseChunks` \u2192 SACK dispatch (`SctpChunk.Parse` :340-341) \u2192 `SctpSackChunk.ParseChunk`. Guard: `ParseChunks` checks only `chunkLength\u003e=4` and `posn+chunkLength\u003c=length` (SctpPacket.cs:195-203). Bypass: `chunkLength=16` is well-formed; the counts are never validated.\n3. Sink: gap-ack loop (SctpSackChunk.cs:146) calls `NetConvert.ParseUInt16(buffer, reportPosn)` with `reportPosn` starting at `startPosn(16)+FIXED_PARAMETERS(12)=28`, climbing `+4` each iteration. Guard: none on the count. Bypass: `NetConvert.ParseUInt16` (NetConvert.cs:30) indexes `buffer[posn]` unchecked.\n4. Impact: at iteration 65529, `reportPosn = 28 + 65529*4 = 262144` \u2192 `buffer[262144]` \u2192 `IndexOutOfRangeException` \u2192 generic `catch` at RTCSctpTransport.cs:356 \u2192 `break` \u2192 receive thread exits, no restart \u2192 association permanently dead.\n\n## Bypass Evidence\n- Unchecked counts at SctpSackChunk.cs:141-142; loops at :146,:154.\n- `NetConvert.ParseUInt16` unchecked indexing (NetConvert.cs:30).\n- `ParseChunks` validates only `chunkLength` (SctpPacket.cs:195-203).\n- OOB math: `28 + 65535*4 = 262168 \u003e 262144`; buffer is 262144 (`DEFAULT_ADVERTISED_RECEIVE_WINDOW`, SctpAssociation.cs:62). `numGapAckBlocks` alone suffices \u2014 the dup-TSN loop is not needed.\n- `DoReceive` catch split: recoverable `catch(ApplicationException)` at :345 (`continue`) vs generic `catch(Exception)` at :356 (`break`); `_receiveThread` started once at :176.\n\n## Affected Versions\n`nuget:SIPSorcery \u003c= 10.0.13` (verified present on release tag v10.0.13 and HEAD da944543).\n\n## Dedup\nNOT a duplicate of GHSA-qmvg-569h-hqrh \u2014 that fix (`fe5a1fa`) touched only `SctpPacket.cs` (the chunk-cursor zero-length infinite loop, CWE-835). This is a distinct out-of-bounds read (CWE-125) in `SctpSackChunk` count loops, untouched by that fix.\n\n## Suggested Fix\nValidate `startPosn + FIXED_PARAMETERS_LENGTH + numGapAckBlocks*4 + numDuplicateTSNs*4 \u003c= posn + chunkLen` before the loops, and/or make `NetConvert.Parse*` bounds-checked, and/or treat `IndexOutOfRangeException`/`ArgumentException` as recoverable in `DoReceive`.\n\n---\nReported by **zx (Jace)** \u2014 GitHub: @manus-use",
"id": "GHSA-jwjp-4649-v8jp",
"modified": "2026-08-12T19:30:43Z",
"published": "2026-08-12T19:30:43Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/sipsorcery-org/sipsorcery/security/advisories/GHSA-jwjp-4649-v8jp"
},
{
"type": "WEB",
"url": "https://github.com/sipsorcery-org/sipsorcery/commit/a2466550bb2a28821c73fb1961bc33dcc467f8cf"
},
{
"type": "PACKAGE",
"url": "https://github.com/sipsorcery-org/sipsorcery"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "SIPSorcery vulnerable to Denial of Service via out-of-bounds read in SCTP SACK chunk parsing"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.