Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions association.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"io"
"math"
"net"
"os"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -1620,13 +1621,22 @@ func (a *Association) AcceptStream() (*Stream, error) {

// createStream creates a stream. The caller should hold the lock and check no stream exists for this id.
func (a *Association) createStream(streamIdentifier uint16, accept bool) *Stream {
var bufferLimit uint64
if limitStr := os.Getenv("SCTP_STREAM_BUFFER_LIMIT"); limitStr != "" {
_, err := fmt.Sscanf(limitStr, "%d", &bufferLimit)

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using fmt.Sscanf with %d format specifier can parse negative numbers into the uint64 variable, which would result in a very large positive value due to unsigned integer wrapping. Consider using strconv.ParseUint instead, which will return an error for negative values and provide better control over the parsing behavior.

Copilot uses AI. Check for mistakes.
if err != nil || bufferLimit == 0 {

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition 'err != nil || bufferLimit == 0' on line 1627 sets bufferLimit to 0 if parsing succeeds but yields a value of 0. This means a user cannot explicitly disable the limit by setting it to 0 in the environment variable - the only way to disable it is to not set the variable at all. If 0 should disable the limit, remove the '|| bufferLimit == 0' check. If 0 should be treated as invalid, consider logging a warning to help users debug misconfiguration.

Suggested change
if err != nil || bufferLimit == 0 {
if err != nil {

Copilot uses AI. Check for mistakes.
bufferLimit = 0
}
}
Comment on lines +1624 to +1630

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The environment variable is read and parsed on every stream creation, which could impact performance when many streams are created. Consider reading the environment variable once during Association initialization and passing the value to createStream(), or caching it in the Association struct.

Copilot uses AI. Check for mistakes.

stream := &Stream{
association: a,
streamIdentifier: streamIdentifier,
reassemblyQueue: newReassemblyQueue(streamIdentifier),
log: a.log,
name: fmt.Sprintf("%d:%s", streamIdentifier, a.name),
writeDeadline: deadline.New(),
bufferLimit: bufferLimit,
}

stream.readNotifier = sync.NewCond(&stream.lock)
Expand Down
11 changes: 11 additions & 0 deletions stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ type Stream struct {
state StreamState
log logging.LeveledLogger
name string
bufferLimit uint64
}

// StreamIdentifier returns the Stream identifier associated to the stream.
Expand Down Expand Up @@ -269,6 +270,16 @@ func (s *Stream) Write(payload []byte) (n int, err error) {

// WriteSCTP writes len(payload) bytes from payload to the DTLS connection.
func (s *Stream) WriteSCTP(payload []byte, ppi PayloadProtocolIdentifier) (int, error) {
// Check buffer limit.
if s.bufferLimit > 0 {
s.lock.RLock()
currentBuffered := s.bufferedAmount
s.lock.RUnlock()
if currentBuffered+uint64(len(payload)) > s.bufferLimit {
return 0, fmt.Errorf("stream buffer limit exceeded: %d bytes", s.bufferLimit)
}
Comment on lines +273 to +280

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a race condition between checking the buffer limit and incrementing the buffered amount. After the check passes (line 278) but before packetize() increments bufferedAmount (line 386), another goroutine could write data. This could allow the buffer to exceed the limit. The buffer limit check should be moved inside the packetize() function under the same lock that increments bufferedAmount, or the lock should be held across both the check and the increment.

Suggested change
// Check buffer limit.
if s.bufferLimit > 0 {
s.lock.RLock()
currentBuffered := s.bufferedAmount
s.lock.RUnlock()
if currentBuffered+uint64(len(payload)) > s.bufferLimit {
return 0, fmt.Errorf("stream buffer limit exceeded: %d bytes", s.bufferLimit)
}
// Check and update buffer under a single lock to avoid races.
if s.bufferLimit > 0 {
s.lock.Lock()
prospective := s.bufferedAmount + uint64(len(payload))
if prospective > s.bufferLimit {
s.lock.Unlock()
return 0, fmt.Errorf("stream buffer limit exceeded: %d bytes", s.bufferLimit)
}
s.bufferedAmount = prospective
s.lock.Unlock()
} else {
// Keep bufferedAmount consistent even when there is no explicit limit.
s.lock.Lock()
s.bufferedAmount += uint64(len(payload))
s.lock.Unlock()

Copilot uses AI. Check for mistakes.
}
Comment on lines +273 to +281

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new buffer limit check logic lacks test coverage. Consider adding tests that verify the buffer limit is enforced correctly, including scenarios where writes succeed when under the limit, fail when exceeding the limit, and handle concurrent writes appropriately.

Copilot uses AI. Check for mistakes.

maxMessageSize := s.association.MaxMessageSize()
if len(payload) > int(maxMessageSize) {
return 0, fmt.Errorf("%w: %v", ErrOutboundPacketTooLarge, maxMessageSize)
Expand Down