Repository: pion/turn (go)

Issue:
Port old fuzzing code to Go's new fuzzing tools
As introduced by Go 1.18

See: https://go.dev/security/fuzz/

Pull request:
Port old fuzzing code to Go's new fuzzing tools
Closes #292 


Patch:
diff --git a/internal/proto/connection_id.go b/internal/proto/connection_id.go
new file mode 100644
index 0000000..4984b47
--- /dev/null
+++ b/internal/proto/connection_id.go
@@ -0,0 +1,39 @@
+package proto
+
+import (
+	"encoding/binary"
+
+	"github.com/pion/stun"
+)
+
+// ConnectionID represents CONNECTION-ID attribute.
+//
+// The CONNECTION-ID attribute uniquely identifies a peer data
+// connection.  It is a 32-bit unsigned integral value.
+//
+// RFC 6062 Section 6.2.1
+type ConnectionID uint32
+
+const connectionIDSize = 4 // uint32: 4 bytes, 32 bits
+
+// AddTo adds CONNECTION-ID to message.
+func (c ConnectionID) AddTo(m *stun.Message) error {
+	v := make([]byte, lifetimeSize)
+	binary.BigEndian.PutUint32(v, uint32(c))
+	m.Add(stun.AttrConnectionID, v)
+	return nil
+}
+
+// GetFrom decodes CONNECTION-ID from message.
+func (c *ConnectionID) GetFrom(m *stun.Message) error {
+	v, err := m.Get(stun.AttrConnectionID)
+	if err != nil {
+		return err
+	}
+	if err = stun.CheckSize(stun.AttrConnectionID, len(v), connectionIDSize); err != nil {
+		return err
+	}
+	_ = v[connectionIDSize-1] // asserting length
+	*(*uint32)(c) = binary.BigEndian.Uint32(v)
+	return nil
+}
diff --git a/internal/proto/dontfrag.go b/internal/proto/dontfrag.go
index eb4d8ca..ac52b2b 100644
--- a/internal/proto/dontfrag.go
+++ b/internal/proto/dontfrag.go
@@ -1,18 +1,45 @@
 package proto
 
-import "github.com/pion/stun"
+import (
+	"github.com/pion/stun"
+)
 
-// DontFragmentAttr represents DONT-FRAGMENT attribute.
-type DontFragmentAttr struct{}
+// DontFragmentAttr is a deprecated alias for DontFragment
+// Deprecated: Please use DontFragment
+type DontFragmentAttr = DontFragment
+
+// DontFragment represents DONT-FRAGMENT attribute.
+//
+// This attribute is used by the client to request that the server set
+// the DF (Don't Fragment) bit in the IP header when relaying the
+// application data onward to the peer.  This attribute has no value
+// part and thus the attribute length field is 0.
+//
+// RFC 5766 Section 14.8
+type DontFragment struct{}
+
+const dontFragmentSize = 0
 
 // AddTo adds DONT-FRAGMENT attribute to message.
-func (DontFragmentAttr) AddTo(m *stun.Message) error {
+func (DontFragment) AddTo(m *stun.Message) error {
 	m.Add(stun.AttrDontFragment, nil)
 	return nil
 }
 
+// GetFrom decodes DONT-FRAGMENT from message.
+func (d *DontFragment) GetFrom(m *stun.Message) error {
+	v, err := m.Get(stun.AttrDontFragment)
+	if err != nil {
+		return err
+	}
+	if err = stun.CheckSize(stun.AttrDontFragment, len(v), dontFragmentSize); err != nil {
+		return err
+	}
+	return nil
+}
+
 // IsSet returns true if DONT-FRAGMENT attribute is set.
-func (DontFragmentAttr) IsSet(m *stun.Message) bool {
+func (DontFragment) IsSet(m *stun.Message) bool {
 	_, err := m.Get(stun.AttrDontFragment)
 	return err == nil
 }
diff --git a/internal/proto/fuzz.go b/internal/proto/fuzz.go
deleted file mode 100644
index 1a171fb..0000000
--- a/internal/proto/fuzz.go
+++ /dev/null
@@ -1,111 +0,0 @@
-// +build gofuzz
-
-package proto
-
-import (
-	"fmt"
-
-	"github.com/pion/stun"
-)
-
-type attr interface {
-	stun.Getter
-	stun.Setter
-}
-
-type attrs []struct {
-	g attr
-	t stun.AttrType
-}
-
-func (a attrs) pick(v byte) struct {
-	g attr
-	t stun.AttrType
-} {
-	idx := int(v) % len(a)
-	return a[idx]
-}
-
-func FuzzSetters(data []byte) int {
-	var (
-		m1 = &stun.Message{
-			Raw: make([]byte, 0, 2048),
-		}
-		m2 = &stun.Message{
-			Raw: make([]byte, 0, 2048),
-		}
-		m3 = &stun.Message{
-			Raw: make([]byte, 0, 2048),
-		}
-	)
-	attributes := attrs{
-		{new(RequestedTransport), stun.AttrRequestedTransport},
-		{new(RelayedAddress), stun.AttrXORRelayedAddress},
-		{new(ChannelNumber), stun.AttrChannelNumber},
-		{new(Data), stun.AttrData},
-		{new(EvenPort), stun.AttrEvenPort},
-		{new(Lifetime), stun.AttrLifetime},
-		{new(ReservationToken), stun.AttrReservationToken},
-	}
-	var firstByte = byte(0)
-	if len(data) > 0 {
-		firstByte = data[0]
-	}
-	a := attributes.pick(firstByte)
-	value := data
-	if len(data) > 1 {
-		value = value[1:]
-	}
-	m1.WriteHeader()
-	m1.Add(a.t, value)
-	err := a.g.GetFrom(m1)
-	if err == stun.ErrAttributeNotFound {
-		fmt.Println("unexpected 404") // nolint
-		panic(err)                    // nolint
-	}
-	if err != nil {
-		return 1
-	}
-	m2.WriteHeader()
-	if err := a.g.AddTo(m2); err != nil {
-		fmt.Println("failed to add attribute to m2") // nolint
-		panic(err)                                   // nolint
-	}
-	m3.WriteHeader()
-	v, err := m2.Get(a.t)
-	if err != nil {
-		panic(err) // nolint
-	}
-	m3.Add(a.t, v)
-
-	if !m2.Equal(m3) {
-		fmt.Println(m2, "not equal", m3) // nolint
-		panic("not equal")               // nolint
-	}
-	return 1
-}
-
-var d = &ChannelData{}
-
-func FuzzChannelData(data []byte) int {
-	d.Reset()
-	if b := bin.Uint16(data[0:4]); b > 20000 {
-		bin.PutUint16(data[0:4], MinChannelNumber-1)
-	} else if b > 40000 {
-		bin.PutUint16(data[0:4], MinChannelNumber+(MaxChannelNumber-MinChannelNumber)%b)
-	}
-	d.Raw = append(d.Raw, data...)
-	if d.Decode() != nil {
-		return 0
-	}
-	d2 := &ChannelData{}
-	d.Encode()
-	if !d.Number.Valid() {
-		return 1
-	}
-	d2.Raw = d.Raw
-	if err := d2.Decode(); err != nil {
-		panic(err) //nolint
-	}
-	return 1
-}


Test patch:
diff --git a/internal/proto/dontfrag_test.go b/internal/proto/dontfrag_test.go
index 07d503c..e866403 100644
--- a/internal/proto/dontfrag_test.go
+++ b/internal/proto/dontfrag_test.go
@@ -7,18 +7,18 @@ import (
 )
 
 func TestDontFragment(t *testing.T) {
-	var DontFragment DontFragmentAttr
+	var dontFrag DontFragment
 
 	t.Run("False", func(t *testing.T) {
 		m := new(stun.Message)
 		m.WriteHeader()
-		if DontFragment.IsSet(m) {
+		if dontFrag.IsSet(m) {
 			t.Error("should not be set")
 		}
 	})
 	t.Run("AddTo", func(t *testing.T) {
 		m := new(stun.Message)
-		if err := DontFragment.AddTo(m); err != nil {
+		if err := dontFrag.AddTo(m); err != nil {
 			t.Error(err)
 		}
 		m.WriteHeader()
@@ -27,11 +27,11 @@ func TestDontFragment(t *testing.T) {
 			if _, err := decoded.Write(m.Raw); err != nil {
 				t.Fatal("failed to decode message:", err)
 			}
-			if !DontFragment.IsSet(m) {
+			if !dontFrag.IsSet(m) {
 				t.Error("should be set")
 			}
 			if wasAllocs(func() {
-				DontFragment.IsSet(m)
+				dontFrag.IsSet(m)
 			}) {
 				t.Error("unexpected allocations")
 			}
diff --git a/internal/proto/fuzz_test.go b/internal/proto/fuzz_test.go
index 5898188..36e53f8 100644
--- a/internal/proto/fuzz_test.go
+++ b/internal/proto/fuzz_test.go
@@ -1,53 +1,126 @@
-// +build gofuzz
-
 package proto
 
 import (
-	"os"
-	"path/filepath"
-	"strings"
+	"encoding/binary"
+	"errors"
+	"fmt"
 	"testing"
+
+	"github.com/pion/stun"
 )
 
-func corpus(t *testing.T, function, typ string) [][]byte {
-	var data [][]byte
-	p := filepath.Join("fuzz", function, typ)
-	f, err := os.Open(p)
-	if err != nil {
-		if os.IsNotExist(err) {
-			t.Skip("does not exist")
-		}
-		t.Fatal(err)
-	}
-	list, err := f.Readdir(-1)
-	if err != nil {
-		t.Fatal(err)
-	}
-	for _, d := range list {
-		if strings.Contains(d.Name(), ".") {
-			// Skipping non-raw files.
-			continue
-		}
-		df, err := os.Open(filepath.Join(p, d.Name()))
+type attr interface {
+	stun.Getter
+	stun.Setter
+}
+
+type attrs []struct {
+	g attr
+	t stun.AttrType
+}
+
+func (a attrs) pick(v byte) struct {
+	g attr
+	t stun.AttrType
+} {
+	idx := int(v) % len(a)
+	return a[idx]
+}
+
+func FuzzSetters(f *testing.F) {
+	f.Fuzz(func(t *testing.T, attrType byte, value []byte) {
+		var (
+			m1 = &stun.Message{
+				Raw: make([]byte, 0, 2048),
+			}
+			m2 = &stun.Message{
+				Raw: make([]byte, 0, 2048),
+			}
+			m3 = &stun.Message{
+				Raw: make([]byte, 0, 2048),
+			}
+		)
+		attributes := attrs{
+			{new(ChannelNumber), stun.AttrChannelNumber},
+			{new(Lifetime), stun.AttrLifetime},
+			{new(XORPeerAddress), stun.AttrXORPeerAddress},
+			{new(Data), stun.AttrData},
+			{new(XORRelayedAddress), stun.AttrXORRelayedAddress},
+			{new(EvenPort), stun.AttrEvenPort},
+			{new(RequestedTransport), stun.AttrRequestedTransport},
+			{new(DontFragment), stun.AttrDontFragment},
+			{new(ReservationToken), stun.AttrReservationToken},
+			{new(ConnectionID), stun.AttrConnectionID},
+			{new(RequestedAddressFamily), stun.AttrRequestedAddressFamily},
+		}
+
+		attr := attributes.pick(attrType)
+
+		m1.WriteHeader()
+		m1.Add(attr.t, value)
+		if err := attr.g.GetFrom(m1); err != nil {
+			if errors.Is(err, stun.ErrAttributeNotFound) {
+				fmt.Println("unexpected 404") // nolint
+				panic(err)                    // nolint
+			}
+			return
+		}
+
+		m2.WriteHeader()
+		if err := attr.g.AddTo(m2); err != nil {
+			fmt.Println("failed to add attribute to m2") // nolint
+			panic(err)                                   // nolint
+		}
+
+		m3.WriteHeader()
+		v, err := m2.Get(attr.t)
 		if err != nil {
-			t.Fatal(err)
-		}
-		buf := make([]byte, 5000)
-		n, _ := df.Read(buf)
-		data = append(data, buf[:n])
-		df.Close()
-	}
-	return data
+			panic(err) // nolint
+		}
+		m3.Add(attr.t, v)
+
+		if !m2.Equal(m3) {
+			fmt.Println(m2, "not equal", m3) // nolint
+			panic("not equal")               // nolint
+		}
+	})
 }
 
-func TestFuzzSetters_Crashers(t *testing.T) {
-	for _, buf := range corpus(t, "turn-setters", "crashers") {
-		FuzzSetters(buf)
-	}
+func FuzzChannelData(f *testing.F) {
+	d := &ChannelData{}
+
+	f.Fuzz(func(t *testing.T, data []byte) {
+		d.Reset()
+
+		if len(data) > channelDataHeaderSize {
+			// Make sure the channel id is in the proper range
+			if b := binary.BigEndian.Uint16(data[0:4]); b > 20000 {
+				binary.BigEndian.PutUint16(data[0:4], MinChannelNumber-1)
+			} else if b > 40000 {
+				binary.BigEndian.PutUint16(data[0:4], MinChannelNumber+(MaxChannelNumber-MinChannelNumber)%b)
+			}
+		}
+
+		d.Raw = append(d.Raw, data...)
+		if d.Decode() != nil {
+			return
+		}
+
+		d.Encode()
+		if !d.Number.Valid() {
+			return
+		}
+
+		d2 := &ChannelData{}
+		d2.Raw = d.Raw
+		if err := d2.Decode(); err != nil {
+			panic(err) //nolint
+		}
+	})
 }
 
-func TestFuzzSetters_Coverage(t *testing.T) {
-	for _, buf := range corpus(t, "turn-setters", "corpus") {
-		FuzzSetters(buf)
-	}
+func FuzzIsChannelData(f *testing.F) {
+	f.Fuzz(func(t *testing.T, data []byte) {
+		IsChannelData(data)
+	})
 }
diff --git a/internal/proto/testdata/fuzz/FuzzChannelData/957e495ca04ae84d31a88d96966c784464d7ccd554158614bd24aa9be2c68c11 b/internal/proto/testdata/fuzz/FuzzChannelData/957e495ca04ae84d31a88d96966c784464d7ccd554158614bd24aa9be2c68c11
new file mode 100644
index 0000000..cf777e2
--- /dev/null
+++ b/internal/proto/testdata/fuzz/FuzzChannelData/957e495ca04ae84d31a88d96966c784464d7ccd554158614bd24aa9be2c68c11
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("@\x00\x00d\x00\x01\x00P!\x12\xa4BE71r/+2+nNzX\x00\x06\x00\tD7X4:3wlY\x00\x00\x00\xc0W\x00\x04\x00\x00\x03\xe7\x80*\x00\b\x1dQ6ڶ[\x16\x93\x00%\x00\x00\x00$\x00\x04n\x00\x1e\xff\x00\b\x00\x14e\xd1\x1a3\x0e\x10J\x9f_Y\x8a\xf4\xabƨ\x05\xf2`\x03π(\x00\x04k3DB@\x00\x02#\x16\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x11\x01,\v\x00\x01 \x00\x01\x00\x00\x00\x00\x01 \x00\x01\x1d\x00\x01\x1a0\x82\x01\x160\x81\xbd\xa0\x03\x02\x01\x02\x02\t\x00\xaf\xe5(q4\v\xd1>0\n\x06\b*\x86H\xce=\x04\x03\x020\x111\x0f0\r\x06\x03U\x04\x03\f\x06WebRTC0\x1e\x17\r180811035200Z\x17\r180911035200Z0\x111\x0f0\r\x06\x03U\x04\x03\f\x06WebRTC0Y0\x13\x06\a*\x86H\xce=\x02\x01\x06\b*\x86H\xce=\x03\x01\a\x03B\x00\x04\x80\x80\xe3H\xbdAF\x9c\xfbz}\xf3\x16go\xd7*\x06!\x17e\xa5\n\x0f\v\aRl\x87-π\t>\xd5ʣ\xf5\xa4\nr]\xd7KA\xb7\x9b\xdd\x19\xeec\fS\x13\xc8`\x1di\x83(l\x87\"\xc10\n\x06\b*\x86H\xce=\x04\x03\x02\x03H\x000E\x02!\x00\xd1:\n\x13\x1b©\xf2z\xbd=LT\x7f~\xf1r\x99j\f\aU\xc7\a\xb6\xa3\xe0H\xd8v-\xed\x02 \x05_\xc8\x18(\x18\xa6D\xa3ӵ\xb1W0L\xc3\xf1B\x1f\xad\xb0bc\xbf\xb4Q\xcd(\xbeK\xc9\xee\x16\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x12\x00-\x10\x00\x00!\x00\x02\x00\x00\x00\x00\x00! \xf7\xe2<\x97\xdfE\xa9n\x13\xcb>v\xb3~\xff^s\xe2\xae\xe0\xb6A])D=\v\xd2OW\x8b~\x16\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x13\x00X\x0f\x00\x00L\x00\x03\x00\x00\x00\x00\x00L\x04\x03\x00H0F\x02!\x00\xfd\xbbt걬\xa1S.j\xc0\xab&}[\x83\xa2K\xb4\xd5\xd7\xd5\x04\x93n'\x85\xe6㈲\xbd\x02!\x00\xf6\xa4W\xb9\xed\xd9\xea\xd5*\x9d\x0e\x9a\x19$\v:h\xb9V\x99Tl\x04O\x86<\xf84\x9b\xc8\x04b\x14\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x14\x00\x01\x01\x16\xfe\xfd\x00\x01\x00\x00\x00\x00\x00\x04\x000\x00\x01\x00\x00\x00\x00\x00\x04\n\xae$!\xe7\xd5Ic*}\xef\x8e\xd0h\x98\xc3ŵ?[\x81*\x96:9\xabl\xdd0;y\xbd\xb27\xf31L\x1d\xa2\x1b")
\ No newline at end of file
diff --git a/internal/proto/testdata/fuzz/FuzzChannelData/a50da1417367eb74b5567c8264828b991bd8cee6e445582860492f99f1b4d676 b/internal/proto/testdata/fuzz/FuzzChannelData/a50da1417367eb74b5567c8264828b991bd8cee6e445582860492f99f1b4d676
new file mode 100644
index 0000000..65644a9
--- /dev/null
+++ b/internal/proto/testdata/fuzz/FuzzChannelData/a50da1417367eb74b5567c8264828b991bd8cee6e445582860492f99f1b4d676
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("@\x00\x02#\x16\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x11\x01,\v\x00\x01 \x00\x01\x00\x00\x00\x00\x01 \x00\x01\x1d\x00\x01\x1a0\x82\x01\x160\x81\xbd\xa0\x03\x02\x01\x02\x02\t\x00\xaf\xe5(q4\v\xd1>0\n\x06\b*\x86H\xce=\x04\x03\x020\x111\x0f0\r\x06\x03U\x04\x03\f\x06WebRTC0\x1e\x17\r180811035200Z\x17\r180911035200Z0\x111\x0f0\r\x06\x03U\x04\x03\f\x06WebRTC0Y0\x13\x06\a*\x86H\xce=\x02\x01\x06\b*\x86H\xce=\x03\x01\a\x03B\x00\x04\x80\x80\xe3H\xbdAF\x9c\xfbz}\xf3\x16go\xd7*\x06!\x17e\xa5\n\x0f\v\aRl\x87-π\t>\xd5ʣ\xf5\xa4\nr]\xd7KA\xb7\x9b\xdd\x19\xeec\fS\x13\xc8`\x1di\x83(l\x87\"\xc10\n\x06\b*\x86H\xce=\x04\x03\x02\x03H\x000E\x02!\x00\xd1:\n\x13\x1b©\xf2z\xbd=LT\x7f~\xf1r\x99j\f\aU\xc7\a\xb6\xa3\xe0H\xd8v-\xed\x02 \x05_\xc8\x18(\x18\xa6D\xa3ӵ\xb1W0L\xc3\xf1B\x1f\xad\xb0bc\xbf\xb4Q\xcd(\xbeK\xc9\xee\x16\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x12\x00-\x10\x00\x00!\x00\x02\x00\x00\x00\x00\x00! \xf7\xe2<\x97\xdfE\xa9n\x13\xcb>v\xb3~\xff^s\xe2\xae\xe0\xb6A])D=\v\xd2OW\x8b~\x16\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x13\x00X\x0f\x00\x00L\x00\x03\x00\x00\x00\x00\x00L\x04\x03\x00H0F\x02!\x00\xfd\xbbt걬\xa1S.j\xc0\xab&}[\x83\xa2K\xb4\xd5\xd7\xd5\x04\x93n'\x85\xe6㈲\xbd\x02!\x00\xf6\xa4W\xb9\xed\xd9\xea\xd5*\x9d\x0e\x9a\x19$\v:h\xb9V\x99Tl\x04O\x86<\xf84\x9b\xc8\x04b\x14\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x14\x00\x01\x01\x16\xfe\xfd\x00\x01\x00\x00\x00\x00\x00\x04\x000\x00\x01\x00\x00\x00\x00\x00\x04\n\xae$!\xe7\xd5Ic*}\xef\x8e\xd0h\x98\xc3ŵ?[\x81*\x96:9\xabl\xdd0;y\xbd\xb27\xf31L\x1d\xa2\x1b")
\ No newline at end of file
diff --git a/internal/proto/testdata/fuzz/FuzzChannelData/c1c1d815dfbe98c11d89ee9fb0436ede58f3451a3a5338cd4e0f6b1122fd600f b/internal/proto/testdata/fuzz/FuzzChannelData/c1c1d815dfbe98c11d89ee9fb0436ede58f3451a3a5338cd4e0f6b1122fd600f
new file mode 100644
index 0000000..d974772
--- /dev/null
+++ b/internal/proto/testdata/fuzz/FuzzChannelData/c1c1d815dfbe98c11d89ee9fb0436ede58f3451a3a5338cd4e0f6b1122fd600f
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("*")