CVE-2022-29190: Loop with Unreachable Exit Condition ('Infinite Loop') (CWE-835)
value: Pion DTLS is a Go implementation of Datagram Transport Layer Security. Prior to version 2.1.4, an attacker can send packets that sends Pion DTLS into an infinite loop when processing. Version 2.1.4 contains a patch for this issue. There are currently no known workarounds available.
CWE description: The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.
CVSS v3 base score: 7.5
Published: 2022-05-21T00:15Z
Repository: https://github.com/pion/dtls
Language: Go
Files changed: fragment_buffer.go, fragment_buffer_test.go
Commit date: 2022-05-10 15:36:11 -0400
Commit message: Don't attempt to append zero length fragments

Before the fragmentBuffer would process zero length fragments. Since
they are zero length the append would not do anything. This would
result in an infinite loop.

Diff: diff --git a/fragment_buffer.go b/fragment_buffer.go
index 0274993..8a00ee7 100644
--- a/fragment_buffer.go
+++ b/fragment_buffer.go
@@ -74,11 +74,11 @@ func (f *fragmentBuffer) pop() (content []byte, epoch uint16) {
 	rawMessage := []byte{}
 	appendMessage = func(targetOffset uint32) bool {
 		for _, f := range frags {
 			if f.handshakeHeader.FragmentOffset == targetOffset {
 				fragmentEnd := (f.handshakeHeader.FragmentOffset + f.handshakeHeader.FragmentLength)
-				if fragmentEnd != f.handshakeHeader.Length {
+				if fragmentEnd != f.handshakeHeader.Length && f.handshakeHeader.FragmentLength != 0 {
 					if !appendMessage(fragmentEnd) {
 						return false
 					}
 				}
 
diff --git a/fragment_buffer_test.go b/fragment_buffer_test.go
index 62c7ead..0f49c75 100644
--- a/fragment_buffer_test.go
+++ b/fragment_buffer_test.go
@@ -71,10 +71,24 @@ func TestFragmentBuffer(t *testing.T) {
 				{0x03, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, 0x01, 0x01},
 				{0x03, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, 0x01, 0x01},
 			},
 			Epoch: 0,
 		},
+		// Assert that a zero length fragment doesn't cause the fragmentBuffer to enter an infinite loop
+		{
+			Name: "Zero Length Fragment",
+			In: [][]byte{
+				{
+					0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00,
+					0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+				},
+			},
+			Expected: [][]byte{
+				{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00},
+			},
+			Epoch: 0,
+		},
 	} {
 		fragmentBuffer := newFragmentBuffer()
 		for _, frag := range test.In {
 			status, err := fragmentBuffer.push(frag)
 			if err != nil {

Vulnerable code: 				if fragmentEnd != f.handshakeHeader.Length {
Fixed code: 				if fragmentEnd != f.handshakeHeader.Length && f.handshakeHeader.FragmentLength != 0 {
		// Assert that a zero length fragment doesn't cause the fragmentBuffer to enter an infinite loop
		{
			Name: "Zero Length Fragment",
			In: [][]byte{
				{
					0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00,
					0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				},
			},
			Expected: [][]byte{
				{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00},
			},
			Epoch: 0,
		},