CVE-2022-29189: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow') (CWE-120)
value: Pion DTLS is a Go implementation of Datagram Transport Layer Security. Prior to version 2.1.4, a buffer that was used for inbound network traffic had no upper limit. Pion DTLS would buffer all network traffic from the remote user until the handshake completes or timed out. An attacker could exploit this to cause excessive memory usage. Version 2.1.4 contains a patch for this issue. There are currently no known workarounds available.
CWE description: The product copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer, leading to a buffer overflow.
CVSS v3 base score: 5.3
Published: 2022-05-21T00:15Z
Repository: https://github.com/pion/dtls
Language: Go
Files changed: errors.go, fragment_buffer.go, fragment_buffer_test.go
Commit date: 2022-05-10 16:28:02 -0400
Commit message: Add limit to fragmentBuffer

Before we imposed no limit on the amount of data we would buffer during
the handshake. This changes adds a 2 megabyte. When the limit is
exceeded the Conn returns an error.

Diff: diff --git a/errors.go b/errors.go
index 042996c..09c6ffe 100644
--- a/errors.go
+++ b/errors.go
@@ -60,10 +60,11 @@ var (
 	errKeySignatureVerifyUnimplemented   = &InternalError{Err: errors.New("unable to verify key signature, unimplemented")}   //nolint:goerr113
 	errLengthMismatch                    = &InternalError{Err: errors.New("data length and declared length do not match")}    //nolint:goerr113
 	errSequenceNumberOverflow            = &InternalError{Err: errors.New("sequence number overflow")}                        //nolint:goerr113
 	errInvalidFSMTransition              = &InternalError{Err: errors.New("invalid state machine transition")}                //nolint:goerr113
 	errFailedToAccessPoolReadBuffer      = &InternalError{Err: errors.New("failed to access pool read buffer")}               //nolint:goerr113
+	errFragmentBufferOverflow            = &InternalError{Err: errors.New("fragment buffer overflow")}                        //nolint:goerr113
 )
 
 // FatalError indicates that the DTLS connection is no longer available.
 // It is mainly caused by wrong configuration of server or client.
 type FatalError = protocol.FatalError
diff --git a/fragment_buffer.go b/fragment_buffer.go
index 8a00ee7..c86a7e8 100644
--- a/fragment_buffer.go
+++ b/fragment_buffer.go
@@ -4,10 +4,13 @@ import (
 	"github.com/pion/dtls/v2/pkg/protocol"
 	"github.com/pion/dtls/v2/pkg/protocol/handshake"
 	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
 )
 
+// 2 megabytes
+const fragmentBufferMaxSize = 2000000
+
 type fragment struct {
 	recordLayerHeader recordlayer.Header
 	handshakeHeader   handshake.Header
 	data              []byte
 }
@@ -21,14 +24,29 @@ type fragmentBuffer struct {
 
 func newFragmentBuffer() *fragmentBuffer {
 	return &fragmentBuffer{cache: map[uint16][]*fragment{}}
 }
 
+// current total size of buffer
+func (f *fragmentBuffer) size() int {
+	size := 0
+	for i := range f.cache {
+		for j := range f.cache[i] {
+			size += len(f.cache[i][j].data)
+		}
+	}
+	return size
+}
+
 // Attempts to push a DTLS packet to the fragmentBuffer
 // when it returns true it means the fragmentBuffer has inserted and the buffer shouldn't be handled
 // when an error returns it is fatal, and the DTLS connection should be stopped
 func (f *fragmentBuffer) push(buf []byte) (bool, error) {
+	if f.size()+len(buf) >= fragmentBufferMaxSize {
+		return false, errFragmentBufferOverflow
+	}
+
 	frag := new(fragment)
 	if err := frag.recordLayerHeader.Unmarshal(buf); err != nil {
 		return false, err
 	}
 
diff --git a/fragment_buffer_test.go b/fragment_buffer_test.go
index 0f49c75..f1013cc 100644
--- a/fragment_buffer_test.go
+++ b/fragment_buffer_test.go
@@ -1,8 +1,9 @@
 package dtls
 
 import (
+	"errors"
 	"reflect"
 	"testing"
 )
 
 func TestFragmentBuffer(t *testing.T) {
@@ -55,11 +56,11 @@ func TestFragmentBuffer(t *testing.T) {
 				{0x0b, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e},
 			},
 			Epoch: 0,
 		},
 		{
-			Name: "Multiple Handshakes in Signle Fragment",
+			Name: "Multiple Handshakes in Single Fragment",
 			In: [][]byte{
 				{
 					0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x30, /* record header */
 					0x03, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, 0x01, 0x01, /*handshake msg 1*/
 					0x03, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, 0x01, 0x01, /*handshake msg 2*/
@@ -111,5 +112,20 @@ func TestFragmentBuffer(t *testing.T) {
 		if frag, _ := fragmentBuffer.pop(); frag != nil {
 			t.Errorf("fragmentBuffer popped single buffer multiple times for '%s'", test.Name)
 		}
 	}
 }
+
+func TestFragmentBuffer_Overflow(t *testing.T) {
+	fragmentBuffer := newFragmentBuffer()
+
+	// Push a buffer that doesn't exceed size limits
+	if _, err := fragmentBuffer.push([]byte{0x16, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, 0x00}); err != nil {
+		t.Fatal(err)
+	}
+
+	// Allocate a buffer that exceeds cache size
+	largeBuffer := make([]byte, fragmentBufferMaxSize)
+	if _, err := fragmentBuffer.push(largeBuffer); !errors.Is(err, errFragmentBufferOverflow) {
+		t.Fatalf("Pushing a large buffer returned (%s) expected(%s)", err, errFragmentBufferOverflow)
+	}
+}

Vulnerable code: 			Name: "Multiple Handshakes in Signle Fragment",
Fixed code: 	errFragmentBufferOverflow            = &InternalError{Err: errors.New("fragment buffer overflow")}                        //nolint:goerr113
// 2 megabytes
const fragmentBufferMaxSize = 2000000

// current total size of buffer
func (f *fragmentBuffer) size() int {
	size := 0
	for i := range f.cache {
		for j := range f.cache[i] {
			size += len(f.cache[i][j].data)
		}
	}
	return size
}

	if f.size()+len(buf) >= fragmentBufferMaxSize {
		return false, errFragmentBufferOverflow
	}

	"errors"
			Name: "Multiple Handshakes in Single Fragment",

func TestFragmentBuffer_Overflow(t *testing.T) {
	fragmentBuffer := newFragmentBuffer()

	// Push a buffer that doesn't exceed size limits
	if _, err := fragmentBuffer.push([]byte{0x16, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, 0x00}); err != nil {
		t.Fatal(err)
	}

	// Allocate a buffer that exceeds cache size
	largeBuffer := make([]byte, fragmentBufferMaxSize)
	if _, err := fragmentBuffer.push(largeBuffer); !errors.Is(err, errFragmentBufferOverflow) {
		t.Fatalf("Pushing a large buffer returned (%s) expected(%s)", err, errFragmentBufferOverflow)
	}
}