THRIFT-5855: go fuzzers
Add fuzzers for go support, to improve the reliability/robustness of the implementation
diff --git a/FUZZING.md b/FUZZING.md
index 0a48246..f239bb7 100644
--- a/FUZZING.md
+++ b/FUZZING.md
@@ -15,7 +15,7 @@
We currently maintain fuzzers for the following languages:
-- Go (needs improvement)
+- Go
- c_glib (partially supported, needs round-trip support)
- C++
diff --git a/lib/go/test/fuzz/Makefile.am b/lib/go/test/fuzz/Makefile.am
index a481e95..e9a960b 100644
--- a/lib/go/test/fuzz/Makefile.am
+++ b/lib/go/test/fuzz/Makefile.am
@@ -19,6 +19,7 @@
gopathfuzz: $(THRIFT) fuzz.go
$(THRIFT) -r --gen go:thrift_import=github.com/apache/thrift/lib/go/thrift,package_prefix=github.com/apache/thrift/lib/go/test/fuzz/gen-go/$(COMPILER_EXTRAFLAG) ../../../../tutorial/tutorial.thrift
+ $(THRIFT) -r --gen go:thrift_import=github.com/apache/thrift/lib/go/thrift,package_prefix=github.com/apache/thrift/lib/go/test/fuzz/gen-go/$(COMPILER_EXTRAFLAG) ../../../../test/FuzzTest.thrift
touch gopathfuzz
check: gopathfuzz
diff --git a/lib/go/test/fuzz/README.md b/lib/go/test/fuzz/README.md
new file mode 100644
index 0000000..54bc4ba
--- /dev/null
+++ b/lib/go/test/fuzz/README.md
@@ -0,0 +1,15 @@
+# Go fuzzing README
+
+To build the fuzz targets, simply run `make check` in this directory
+
+To reproduce a bug, update the code in the fuzz_test.go file, pass the right input buffer in there and update the code to call the relevant function.
+
+We currently have the following fuzz targets:
+
+* FuzzTutorial -- a fuzzer which spins up an mini server and fuzzes it with random data, following the tutorial example
+* FuzzParseCompact -- fuzzes the deserialization of the Compact protocol
+* FuzzParseBinary -- fuzzes the deserialization of the Binary protocol
+* FuzzParseJson -- fuzzes the deserialization of the JSON protocol
+* FuzzRoundtripCompact -- fuzzes the roundtrip of the Compact protocol
+* FuzzRoundtripBinary -- fuzzes the roundtrip of the Binary protocol
+* FuzzRoundtripJson -- fuzzes the roundtrip of the JSON protocol
diff --git a/lib/go/test/fuzz/fuzz.go b/lib/go/test/fuzz/fuzz.go
index 0698379..bad6b41 100644
--- a/lib/go/test/fuzz/fuzz.go
+++ b/lib/go/test/fuzz/fuzz.go
@@ -29,11 +29,15 @@
"github.com/apache/thrift/lib/go/test/fuzz/gen-go/shared"
"github.com/apache/thrift/lib/go/test/fuzz/gen-go/tutorial"
+ "github.com/apache/thrift/lib/go/test/fuzz/gen-go/fuzztest"
"github.com/apache/thrift/lib/go/thrift"
)
const nbFuzzedProtocols = 2
+// 10MB message size limit to prevent over-allocation during fuzzing
+const FUZZ_MAX_MESSAGE_SIZE = 10 * 1024 * 1024
+
func fuzzChooseProtocol(d byte, t thrift.TTransport) thrift.TProtocol {
switch d % nbFuzzedProtocols {
default:
@@ -42,10 +46,12 @@
return thrift.NewTBinaryProtocolFactoryConf(nil).GetProtocol(t)
case 1:
return thrift.NewTCompactProtocolFactoryConf(nil).GetProtocol(t)
+ case 2:
+ return thrift.NewTJSONProtocolFactory().GetProtocol(t)
}
}
-func Fuzz(data []byte) int {
+func FuzzTutorial(data []byte) int {
if len(data) < 2 {
return 0
}
@@ -142,3 +148,242 @@
fmt.Print("zip()\n")
return nil
}
+
+func FuzzParseBinary(data []byte) int {
+ // Skip if input is too small
+ if len(data) < 1 {
+ return 0
+ }
+
+ // Create transport and protocol
+ transport := thrift.NewTMemoryBufferLen(len(data))
+ defer func() {
+ transport.Close()
+ // Reset the buffer to release memory
+ transport.Buffer.Reset()
+ }()
+ transport.Write(data)
+ config := &thrift.TConfiguration{
+ MaxMessageSize: FUZZ_MAX_MESSAGE_SIZE,
+ }
+ protocol := thrift.NewTBinaryProtocolFactoryConf(config).GetProtocol(transport)
+
+ // Try to read the FuzzTest structure
+ fuzzTest := fuzztest.NewFuzzTest()
+ err := fuzzTest.Read(context.Background(), protocol)
+ if err != nil {
+ // Invalid input, but not a crash
+ return 0
+ }
+
+ // Successfully parsed
+ return 1
+}
+
+func FuzzParseCompact(data []byte) int {
+ // Skip if input is too small
+ if len(data) < 1 {
+ return 0
+ }
+
+ // Create transport and protocol
+ transport := thrift.NewTMemoryBufferLen(len(data))
+ defer func() {
+ transport.Close()
+ // Reset the buffer to release memory
+ transport.Buffer.Reset()
+ }()
+ transport.Write(data)
+ config := &thrift.TConfiguration{
+ MaxMessageSize: FUZZ_MAX_MESSAGE_SIZE,
+ }
+ protocol := thrift.NewTCompactProtocolFactoryConf(config).GetProtocol(transport)
+
+ // Try to read the FuzzTest structure
+ fuzzTest := fuzztest.NewFuzzTest()
+ err := fuzzTest.Read(context.Background(), protocol)
+ if err != nil {
+ // Invalid input, but not a crash
+ return 0
+ }
+
+ // Successfully parsed
+ return 1
+}
+
+func FuzzParseJson(data []byte) int {
+ // Skip if input is too small
+ if len(data) < 1 {
+ return 0
+ }
+
+ // Create transport and protocol
+ transport := thrift.NewTMemoryBufferLen(len(data))
+ defer func() {
+ transport.Close()
+ // Reset the buffer to release memory
+ transport.Buffer.Reset()
+ }()
+ transport.Write(data)
+ protocol := thrift.NewTJSONProtocolFactory().GetProtocol(transport)
+
+ // Try to read the FuzzTest structure
+ fuzzTest := fuzztest.NewFuzzTest()
+ err := fuzzTest.Read(context.Background(), protocol)
+ if err != nil {
+ // Invalid input, but not a crash
+ return 0
+ }
+
+ // Successfully parsed
+ return 1
+}
+
+func FuzzRoundtripBinary(data []byte) int {
+ // Skip if input is too small
+ if len(data) < 1 {
+ return 0
+ }
+
+ config := &thrift.TConfiguration{
+ MaxMessageSize: FUZZ_MAX_MESSAGE_SIZE,
+ }
+
+ // First parse
+ transport := thrift.NewTMemoryBufferLen(len(data))
+ transport.Write(data)
+ protocol := thrift.NewTBinaryProtocolFactoryConf(config).GetProtocol(transport)
+
+ // Try to read the FuzzTest structure
+ test1 := fuzztest.NewFuzzTest()
+ err := test1.Read(context.Background(), protocol)
+ if err != nil {
+ // Invalid input, but not a crash
+ return 0
+ }
+
+ // Serialize back
+ outTransport := thrift.NewTMemoryBuffer()
+ outProtocol := thrift.NewTBinaryProtocolFactoryConf(config).GetProtocol(outTransport)
+ err = test1.Write(context.Background(), outProtocol)
+ if err != nil {
+ return 0
+ }
+
+ // Get serialized data and deserialize again
+ serialized := outTransport.Bytes()
+ reTransport := thrift.NewTMemoryBufferLen(len(serialized))
+ reTransport.Write(serialized)
+ reProtocol := thrift.NewTBinaryProtocolFactoryConf(config).GetProtocol(reTransport)
+
+ test2 := fuzztest.NewFuzzTest()
+ err = test2.Read(context.Background(), reProtocol)
+ if err != nil {
+ return 0
+ }
+
+ // Verify equality
+ if !test1.Equals(test2) {
+ panic("Roundtrip failed: objects not equal after deserialization")
+ }
+
+ return 1
+}
+
+func FuzzRoundtripCompact(data []byte) int {
+ // Skip if input is too small
+ if len(data) < 1 {
+ return 0
+ }
+
+ config := &thrift.TConfiguration{
+ MaxMessageSize: FUZZ_MAX_MESSAGE_SIZE,
+ }
+
+ // First parse
+ transport := thrift.NewTMemoryBufferLen(len(data))
+ transport.Write(data)
+ protocol := thrift.NewTCompactProtocolFactoryConf(config).GetProtocol(transport)
+
+ // Try to read the FuzzTest structure
+ test1 := fuzztest.NewFuzzTest()
+ err := test1.Read(context.Background(), protocol)
+ if err != nil {
+ // Invalid input, but not a crash
+ return 0
+ }
+
+ // Serialize back
+ outTransport := thrift.NewTMemoryBuffer()
+ outProtocol := thrift.NewTCompactProtocolFactoryConf(config).GetProtocol(outTransport)
+ err = test1.Write(context.Background(), outProtocol)
+ if err != nil {
+ return 0
+ }
+
+ // Get serialized data and deserialize again
+ serialized := outTransport.Bytes()
+ reTransport := thrift.NewTMemoryBufferLen(len(serialized))
+ reTransport.Write(serialized)
+ reProtocol := thrift.NewTCompactProtocolFactoryConf(config).GetProtocol(reTransport)
+
+ test2 := fuzztest.NewFuzzTest()
+ err = test2.Read(context.Background(), reProtocol)
+ if err != nil {
+ return 0
+ }
+
+ // Verify equality
+ if !test1.Equals(test2) {
+ panic("Roundtrip failed: objects not equal after deserialization")
+ }
+
+ return 1
+}
+
+func FuzzRoundtripJson(data []byte) int {
+ // Skip if input is too small
+ if len(data) < 1 {
+ return 0
+ }
+
+ // First parse
+ transport := thrift.NewTMemoryBufferLen(len(data))
+ transport.Write(data)
+ protocol := thrift.NewTJSONProtocolFactory().GetProtocol(transport)
+
+ // Try to read the FuzzTest structure
+ test1 := fuzztest.NewFuzzTest()
+ err := test1.Read(context.Background(), protocol)
+ if err != nil {
+ // Invalid input, but not a crash
+ return 0
+ }
+
+ // Serialize back
+ outTransport := thrift.NewTMemoryBuffer()
+ outProtocol := thrift.NewTJSONProtocolFactory().GetProtocol(outTransport)
+ err = test1.Write(context.Background(), outProtocol)
+ if err != nil {
+ return 0
+ }
+
+ // Get serialized data and deserialize again
+ serialized := outTransport.Bytes()
+ reTransport := thrift.NewTMemoryBufferLen(len(serialized))
+ reTransport.Write(serialized)
+ reProtocol := thrift.NewTJSONProtocolFactory().GetProtocol(reTransport)
+
+ test2 := fuzztest.NewFuzzTest()
+ err = test2.Read(context.Background(), reProtocol)
+ if err != nil {
+ return 0
+ }
+
+ // Verify equality
+ if !test1.Equals(test2) {
+ panic("Roundtrip failed: objects not equal after deserialization")
+ }
+
+ return 1
+}
diff --git a/lib/go/test/fuzz/fuzz_test.go b/lib/go/test/fuzz/fuzz_test.go
index 2983e0f..73eb072 100644
--- a/lib/go/test/fuzz/fuzz_test.go
+++ b/lib/go/test/fuzz/fuzz_test.go
@@ -26,5 +26,5 @@
)
func TestFuzz(t *testing.T) {
- Fuzz([]byte{1, 2, 3})
+ FuzzTutorial([]byte{1, 2, 3})
}