CVE-2022-29189 (published 2022-05-21T00:15Z)

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.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:N/A:P)
CVSS v3 base score: 5.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L)

Weakness information:
cwe_id: CWE-120
cwe_name: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
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.
extended_description: A buffer overflow condition exists when a product attempts to put more data in a buffer than it can hold, or when it attempts to put data in a memory area outside of the boundaries of a buffer. The simplest type of error, and the most common cause of buffer overflows, is the "classic" case in which the product copies the buffer without restricting how much is copied. Other variants exist, but the existence of a classic overflow strongly suggests that the programmer is not considering even the most basic of security protections.
         

Fix information:
repo_url: https://github.com/pion/dtls
commit_details: repo_url: https://github.com/pion/dtls
author: Sean DuBois
author_date: 2022-05-10 15:36:31-04:00
msg: 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.
repository: repo_url: https://github.com/pion/dtls
repo_name: pion/dtls
description: DTLS 1.2 Server/Client implementation for Go
repo_language: Go
owner: pion
file_changes: filename: errors.go
change_type: ModificationType.MODIFY
diff: @@ -62,6 +62,7 @@ var (
 	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.

programming_language: Go
filename: fragment_buffer.go
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,9 @@ import (
 	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
 )
 
+// 2 megabytes
+const fragmentBufferMaxSize = 2000000
+
 type fragment struct {
 	recordLayerHeader recordlayer.Header
 	handshakeHeader   handshake.Header
@@ -23,10 +26,25 @@ 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

programming_language: Go
filename: fragment_buffer_test.go
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,7 @@
 package dtls
 
 import (
+	"errors"
 	"reflect"
 	"testing"
 )
@@ -57,7 +58,7 @@ func TestFragmentBuffer(t *testing.T) {
 			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 */
@@ -113,3 +114,18 @@ func TestFragmentBuffer(t *testing.T) {
 		}
 	}
 }
+
+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)
+	}
+}

programming_language: Go