# SIPSorcery: Malformed UDP datagram crashes TurnServer receive loop with no restart, disabling TURN UDP relay for all clients (DoS)

Single pre-auth UDP datagram with first byte 0x80 makes STUNHeader.ParseSTUNHeader throw, unwinding TurnServer's receive loop and permanently killing TURN UDP relay for all clients.

- Payout: Research
- Severity: high
- Program: SIPSorcery
- Found by: manus-use
- Disclosed: 2026-08-12
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-pfvm-w89x-94jw
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-pfvm-w89x-94jw
- Tags: area:devices domain:network impact:dos kind:research platform:ghsa sev:high tech:dotnet technique:incomplete-fix technique:source-review vuln:dos

**Target:** TURN server UDP listener (TurnServer.ReceiveUdpAsync, default port 3478)
**Root cause:** ReceiveUdpAsync's generic catch sits outside the while loop and only wraps the socket receive, so an exception from HandleUdpDatagram/ParseSTUNMessage escapes the loop; Start() launches the loop fire-and-forget with no restart.
**Steps:**
1. Send one UDP datagram to the TURN port (default 3478) with first byte `0x80`, e.g. `80 00 00 00`.
2. `(data[0] & 0xC0) == 0x80 != 0x40`, so the ChannelData branch is skipped and `ParseSTUNMessage` is called.
3. `ParseSTUNHeader` hits `if ((Array[startIndex] & 0xC0) != 0) throw new ApplicationException(...)` (STUNHeader.cs:169-172).
4. Exception unwinds past the while into `catch (Exception)` at TurnServer.cs:573, is logged, method returns; no restart re-invokes it.
**How found:** Code review of TurnServer.cs: noticed the generic catch was lexically outside the while loop and only the socket receive was inside the inner try, while HandleUdpDatagram was unguarded; then traced ParseSTUNMessage into ParseSTUNHeader to find the throw reachable from a single byte, and confirmed Start() fire-and-forget with no supervision.
**Exploitation:** Unauthenticated attacker sends a single crafted UDP packet; STUN parsing precedes any TURN allocation or credential check, so no auth needed. The receive loop terminates and TURN UDP relay stays dead for every client until the process is restarted.
**Insight:** In fire-and-forget async receive loops, check whether the catch is inside or outside the while and whether per-datagram parsing is inside the try — a single unguarded parser throw on attacker-controlled bytes silently kills the whole listener with no restart.

## Proof of concept (copied exactly from the original report)

**Single UDP datagram crashing TURN receive loop**

```
Send one UDP datagram to the TURN port (default 3478) with first byte `0x80` (e.g. `80 00 00 00`). `0x80 & 0xC0 = 0x80 ≠ 0x40` → not ChannelData → `ParseSTUNMessage` → `ParseSTUNHeader` executes `if ((Array[startIndex] & 0xC0) != 0) throw new ApplicationException(...)` (STUNHeader.cs:169-172); `0x80 & 0xC0 = 0x80 ≠ 0` → throws.
```

