Source file src/internal/zstd/fuzz_test.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package zstd
     6  
     7  import (
     8  	"bytes"
     9  	"io"
    10  	"os"
    11  	"os/exec"
    12  	"testing"
    13  )
    14  
    15  // badStrings is some inputs that FuzzReader failed on earlier.
    16  var badStrings = []string{
    17  	"(\xb5/\xfdd00,\x05\x00\xc4\x0400000000000000000000000000000000000000000000000000000000000000000000000000000 \xa07100000000000000000000000000000000000000000000000000000000000000000000000000aM\x8a2y0B\b",
    18  	"(\xb5/\xfd00$\x05\x0020 00X70000a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    19  	"(\xb5/\xfd00$\x05\x0020 00B00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    20  	"(\xb5/\xfd00}\x00\x0020\x00\x9000000000000",
    21  	"(\xb5/\xfd00}\x00\x00&0\x02\x830!000000000",
    22  	"(\xb5/\xfd\x1002000$\x05\x0010\xcc0\xa8100000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    23  	"(\xb5/\xfd\x1002000$\x05\x0000\xcc0\xa8100d\x0000001000000000000000000000000000000000000000000000000000000000000000000000000\x000000000000000000000000000000000000000000000000000000000000000000000000000000",
    24  	"(\xb5/\xfd001\x00\x0000000000000000000",
    25  	"(\xb5/\xfd00\xec\x00\x00&@\x05\x05A7002\x02\x00\x02\x00\x02\x0000000000000000",
    26  	"(\xb5/\xfd00\xec\x00\x00V@\x05\x0517002\x02\x00\x02\x00\x02\x0000000000000000",
    27  	"\x50\x2a\x4d\x18\x02\x00\x00\x00",
    28  	"(\xb5/\xfd\xe40000000\xfa20\x000",
    29  }
    30  
    31  // This is a simple fuzzer to see if the decompressor panics.
    32  func FuzzReader(f *testing.F) {
    33  	for _, test := range tests {
    34  		f.Add([]byte(test.compressed))
    35  	}
    36  	for _, s := range badStrings {
    37  		f.Add([]byte(s))
    38  	}
    39  	f.Fuzz(func(t *testing.T, b []byte) {
    40  		r := NewReader(bytes.NewReader(b))
    41  		io.Copy(io.Discard, r)
    42  	})
    43  }
    44  
    45  // Fuzz test to verify that what we decompress is what we compress.
    46  // This isn't a great fuzz test because the fuzzer can't efficiently
    47  // explore the space of decompressor behavior, since it can't see
    48  // what the compressor is doing. But it's better than nothing.
    49  func FuzzDecompressor(f *testing.F) {
    50  	zstd := findZstd(f)
    51  
    52  	for _, test := range tests {
    53  		f.Add([]byte(test.uncompressed))
    54  	}
    55  
    56  	// Add some larger data, as that has more interesting compression.
    57  	f.Add(bytes.Repeat([]byte("abcdefghijklmnop"), 256))
    58  	var buf bytes.Buffer
    59  	for i := 0; i < 256; i++ {
    60  		buf.WriteByte(byte(i))
    61  	}
    62  	f.Add(bytes.Repeat(buf.Bytes(), 64))
    63  	f.Add(bigData(f))
    64  
    65  	f.Fuzz(func(t *testing.T, b []byte) {
    66  		cmd := exec.Command(zstd, "-z")
    67  		cmd.Stdin = bytes.NewReader(b)
    68  		var compressed bytes.Buffer
    69  		cmd.Stdout = &compressed
    70  		cmd.Stderr = os.Stderr
    71  		if err := cmd.Run(); err != nil {
    72  			t.Errorf("running zstd failed: %v", err)
    73  		}
    74  
    75  		r := NewReader(bytes.NewReader(compressed.Bytes()))
    76  		got, err := io.ReadAll(r)
    77  		if err != nil {
    78  			t.Fatal(err)
    79  		}
    80  		if !bytes.Equal(got, b) {
    81  			showDiffs(t, got, b)
    82  		}
    83  	})
    84  }
    85  
    86  // Fuzz test to check that if we can decompress some data,
    87  // so can zstd, and that we get the same result.
    88  func FuzzReverse(f *testing.F) {
    89  	zstd := findZstd(f)
    90  
    91  	for _, test := range tests {
    92  		f.Add([]byte(test.compressed))
    93  	}
    94  
    95  	// Set a hook to reject some cases where we don't match zstd.
    96  	fuzzing = true
    97  	defer func() { fuzzing = false }()
    98  
    99  	f.Fuzz(func(t *testing.T, b []byte) {
   100  		r := NewReader(bytes.NewReader(b))
   101  		goExp, goErr := io.ReadAll(r)
   102  
   103  		cmd := exec.Command(zstd, "-d")
   104  		cmd.Stdin = bytes.NewReader(b)
   105  		var uncompressed bytes.Buffer
   106  		cmd.Stdout = &uncompressed
   107  		cmd.Stderr = os.Stderr
   108  		zstdErr := cmd.Run()
   109  		zstdExp := uncompressed.Bytes()
   110  
   111  		if goErr == nil && zstdErr == nil {
   112  			if !bytes.Equal(zstdExp, goExp) {
   113  				showDiffs(t, zstdExp, goExp)
   114  			}
   115  		} else {
   116  			// Ideally we should check that this package and
   117  			// the zstd program both fail or both succeed,
   118  			// and that if they both fail one byte sequence
   119  			// is an exact prefix of the other.
   120  			// Actually trying this proved to be frustrating,
   121  			// as the zstd program appears to accept invalid
   122  			// byte sequences using rules that are difficult
   123  			// to determine.
   124  			// So we just check the prefix.
   125  
   126  			c := len(goExp)
   127  			if c > len(zstdExp) {
   128  				c = len(zstdExp)
   129  			}
   130  			goExp = goExp[:c]
   131  			zstdExp = zstdExp[:c]
   132  			if !bytes.Equal(goExp, zstdExp) {
   133  				t.Error("byte mismatch after error")
   134  				t.Logf("Go error: %v\n", goErr)
   135  				t.Logf("zstd error: %v\n", zstdErr)
   136  				showDiffs(t, zstdExp, goExp)
   137  			}
   138  		}
   139  	})
   140  }
   141  

View as plain text