Fix uint16 overflow in consumeSingleTURNFrame - #593
Conversation
A ChannelData frame with a length field >= 0xFFFC plus the 4-byte header overflowed uint16 arithmetic, wrapping the frame size to 0. ReadFrom then returned a zero-size frame without consuming its buffer, causing the server read loop to spin at 100% CPU.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #593 +/- ##
==========================================
+ Coverage 82.42% 82.52% +0.10%
==========================================
Files 46 46
Lines 3305 3307 +2
==========================================
+ Hits 2724 2729 +5
+ Misses 377 374 -3
Partials 204 204
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
A complete ChannelData frame can be larger than the caller's buffer. ReadFrom reported the full frame size, so Client.Listen sliced its 65,535-byte buffer out of range and panicked when a 65,536-byte frame arrived over NewSTUNConn. Consume the whole frame to keep the framing aligned, but report only the bytes actually copied.
|
@fallenmi Thanks for the thorough review. Both points are addressed in the latest push (ca3db96):
Verified locally: proto and root package -race suites pass, golangci-lint reports 0 issues, and the existing overflow/boundary tests are unchanged. |
JoTurk
left a comment
There was a problem hiding this comment.
The 65536-byte client-crash is unrealistic, Turn server buffer is limited to 1600-bytes and pion/webrtc has a 1200 mtu.
this PR should be only limited to safe frame-length handling, and making sure we don't overflow, and we should reject frames whose declared size exceeds len(payload)
Narrow the change to safe frame-length handling per review: - Keep the uint32 widening so a ChannelData/STUN length near the uint16 maximum no longer wraps to a small frame size. - Replace the truncate-and-deliver path with an explicit rejection: a frame whose declared size exceeds len(payload) returns errTURNFrameTooLarge after consuming the frame, so the stream framing stays aligned and no caller ever sees n > len(payload). - Fold the regression assertions into the existing stun_conn_test.go instead of separate regression test files.
|
@JoTurk agreed on both points — the PR is now narrowed to exactly what you described. Pushed in Dropped the client-crash framing. You're right that a 65,536-byte frame is not reachable through the pion stack (server What remains, per your direction:
if n > len(payload) {
s.buff = s.buff[n:]
return 0, nil, errTURNFrameTooLarge
}The frame is consumed so stream framing stays aligned and the next frame still parses; no caller ever observes Tests: folded into the existing Diff is now 2 files, +112/-11, confined to Verified: |
0b539fa to
1de46c2
Compare
|
Rebased the three commits to use a consistent author identity ( To summarise what the current head contains, per your direction:
Could someone approve the workflow run? CI has not triggered yet — I believe it needs a maintainer to approve since this is my first contribution here. |
|
Hi @JoTurk, I've addressed all your review feedback:
Could you please take another look and, if everything looks good, approve the CI workflow run? (First-contribution restriction requires maintainer approval to trigger GitHub Actions.) Thank you! |
SUMMARY
A malformed ChannelData frame with a length field in [0xFFFC, 0xFFFF]
caused the frame-size computation in consumeSingleTURNFrame to
overflow uint16, wrapping to 0. STUNConn.ReadFrom then reported a
zero-size frame with a nil error and never consumed the buffered
data, leaving the server read loop spinning at 100% CPU. A single
4-byte packet over TCP triggers this, allowing a remote DoS.
The frame size is now computed with uint32 arithmetic, and the
padding math was rewritten in an equivalent overflow-safe form.
The same overflow class in the STUN branch is covered by the fix.
TEST
fail before the fix (n=0, err=nil) and pass after.
formula matches the old one for all 65536 length values.
size (65540), zero-length and non-multiple padding.
CONTEXT
Found while reviewing the codebase for potential protocol-handling
bugs. The fix is limited to internal/proto and changes no public API.