CVE-2019-14243: Improper Input Validation (CWE-20)
value: headerv2.go in mastercactapus proxyprotocol before 0.0.2, as used in the mastercactapus caddy-proxyprotocol plugin through 0.0.2 for Caddy, allows remote attackers to cause a denial of service (webserver panic and daemon crash) via a crafted HAProxy PROXY v2 request with truncated source/destination address data.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 7.5
Published: 2019-07-23T22:15Z
Repository: https://github.com/mastercactapus/proxyprotocol
Language: Go
Files changed: headerv2.go, headerv2_test.go, parse_test.go
Commit date: 2019-07-23 08:34:30 -0500
Commit message: Add test and fix for malformed/truncated header

Diff: diff --git a/headerv2.go b/headerv2.go
index e3565bf..032f90e 100644
--- a/headerv2.go
+++ b/headerv2.go
@@ -48,26 +48,37 @@ func parseV2(r *bufio.Reader) (*HeaderV2, error) {
 	if h.Command > CmdProxy {
 		return nil, &InvalidHeaderErr{Read: buf[:16], error: errors.New("invalid v2 command")}
 	}
 
 	// highest 4 indicate address family
-	if (rawHdr.FamProto >> 4) > 3 {
+	switch rawHdr.FamProto >> 4 {
+	case 0: // local
+		if rawHdr.Len != 0 {
+			return nil, &InvalidHeaderErr{Read: buf[:16], error: errors.New("invalid length")}
+		}
+	case 1: // ipv4
+		if rawHdr.Len != 12 {
+			return nil, &InvalidHeaderErr{Read: buf[:16], error: errors.New("invalid length")}
+		}
+	case 2: // ipv6
+		if rawHdr.Len != 36 {
+			return nil, &InvalidHeaderErr{Read: buf[:16], error: errors.New("invalid length")}
+		}
+	case 3: // unix
+		if rawHdr.Len != 216 {
+			return nil, &InvalidHeaderErr{Read: buf[:16], error: errors.New("invalid length")}
+		}
+	default:
 		return nil, &InvalidHeaderErr{Read: buf[:16], error: errors.New("invalid v2 address family")}
 	}
 
 	// lowest 4 = transport protocol (0xf == 0b00001111)
 	if (rawHdr.FamProto & 0xf) > 2 {
 		return nil, &InvalidHeaderErr{Read: buf[:16], error: errors.New("invalid v2 transport protocol")}
 	}
 
-	if 16+int(rawHdr.Len) > len(buf) {
-		newBuf := make([]byte, 16+int(rawHdr.Len))
-		copy(newBuf, buf[:16])
-		buf = newBuf
-	} else {
-		buf = buf[:16+int(rawHdr.Len)]
-	}
+	buf = buf[:16+int(rawHdr.Len)]
 
 	n, err = io.ReadFull(r, buf[16:])
 	if err != nil {
 		return nil, &InvalidHeaderErr{Read: buf[:16+n], error: err}
 	}
diff --git a/headerv2_test.go b/headerv2_test.go
index 352c8b5..42ac127 100644
--- a/headerv2_test.go
+++ b/headerv2_test.go
@@ -31,11 +31,13 @@ func TestHeaderV2(t *testing.T) {
 			var buf bytes.Buffer
 			for _, s := range exp {
 				buf.Write(s.value)
 			}
 			hdr, err := Parse(bufio.NewReader(&buf))
-			assert.NoError(t, err)
+			if !assert.NoError(t, err) {
+				return
+			}
 			assert.IsType(t, &HeaderV2{}, hdr, "Header Type")
 			p := hdr.(*HeaderV2)
 			assert.Equal(t, h.Command, p.Command, "Command")
 			if h.Src != nil {
 				assert.NotNil(t, p.Src)
diff --git a/parse_test.go b/parse_test.go
new file mode 100644
index 0000000..48e2eaa
--- /dev/null
+++ b/parse_test.go
@@ -0,0 +1,28 @@
+package proxyprotocol
+
+import (
+	"bufio"
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestParse_Malformed(t *testing.T) {
+	data := []byte{
+		// PROXY protocol v2 magic header
+		0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
+		// v2 version, PROXY cmd
+		0x21,
+		// TCP, IPv4 (also works with 0x13,0x21,0x22,0x31,0x32)
+		0x12,
+		// Length
+		0x00, 0x00,
+		// src/dest address data _should_ be here but is omitted.
+	}
+
+	_, err := Parse(
+		bufio.NewReader(
+			bytes.NewReader(data)))
+	assert.Error(t, err)
+}

Vulnerable code: 	if (rawHdr.FamProto >> 4) > 3 {
	if 16+int(rawHdr.Len) > len(buf) {
		newBuf := make([]byte, 16+int(rawHdr.Len))
		copy(newBuf, buf[:16])
		buf = newBuf
	} else {
		buf = buf[:16+int(rawHdr.Len)]
	}
			assert.NoError(t, err)
Fixed code: 	switch rawHdr.FamProto >> 4 {
	case 0: // local
		if rawHdr.Len != 0 {
			return nil, &InvalidHeaderErr{Read: buf[:16], error: errors.New("invalid length")}
		}
	case 1: // ipv4
		if rawHdr.Len != 12 {
			return nil, &InvalidHeaderErr{Read: buf[:16], error: errors.New("invalid length")}
		}
	case 2: // ipv6
		if rawHdr.Len != 36 {
			return nil, &InvalidHeaderErr{Read: buf[:16], error: errors.New("invalid length")}
		}
	case 3: // unix
		if rawHdr.Len != 216 {
			return nil, &InvalidHeaderErr{Read: buf[:16], error: errors.New("invalid length")}
		}
	default:
	buf = buf[:16+int(rawHdr.Len)]
			if !assert.NoError(t, err) {
				return
			}
package proxyprotocol

import (
	"bufio"
	"bytes"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestParse_Malformed(t *testing.T) {
	data := []byte{
		// PROXY protocol v2 magic header
		0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
		// v2 version, PROXY cmd
		0x21,
		// TCP, IPv4 (also works with 0x13,0x21,0x22,0x31,0x32)
		0x12,
		// Length
		0x00, 0x00,
		// src/dest address data _should_ be here but is omitted.
	}

	_, err := Parse(
		bufio.NewReader(
			bytes.NewReader(data)))
	assert.Error(t, err)
}