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  }
    29  
    30  // This is a simple fuzzer to see if the decompressor panics.
    31  func FuzzReader(f *testing.F) {
    32  	for _, test := range tests {
    33  		f.Add([]byte(test.compressed))
    34  	}
    35  	for _, s := range badStrings {
    36  		f.Add([]byte(s))
    37  	}
    38  	f.Fuzz(func(t *testing.T, b []byte) {
    39  		r := NewReader(bytes.NewReader(b))
    40  		io.Copy(io.Discard, r)
    41  	})
    42  }
    43  
    44  // Fuzz test to verify that what we decompress is what we compress.
    45  // This isn't a great fuzz test because the fuzzer can't efficiently
    46  // explore the space of decompressor behavior, since it can't see
    47  // what the compressor is doing. But it's better than nothing.
    48  func FuzzDecompressor(f *testing.F) {
    49  	zstd := findZstd(f)
    50  
    51  	for _, test := range tests {
    52  		f.Add([]byte(test.uncompressed))
    53  	}
    54  
    55  	// Add some larger data, as that has more interesting compression.
    56  	f.Add(bytes.Repeat([]byte("abcdefghijklmnop"), 256))
    57  	var buf bytes.Buffer
    58  	for i := 0; i < 256; i++ {
    59  		buf.WriteByte(byte(i))
    60  	}
    61  	f.Add(bytes.Repeat(buf.Bytes(), 64))
    62  	f.Add(bigData(f))
    63  
    64  	f.Fuzz(func(t *testing.T, b []byte) {
    65  		cmd := exec.Command(zstd, "-z")
    66  		cmd.Stdin = bytes.NewReader(b)
    67  		var compressed bytes.Buffer
    68  		cmd.Stdout = &compressed
    69  		cmd.Stderr = os.Stderr
    70  		if err := cmd.Run(); err != nil {
    71  			t.Errorf("running zstd failed: %v", err)
    72  		}
    73  
    74  		r := NewReader(bytes.NewReader(compressed.Bytes()))
    75  		got, err := io.ReadAll(r)
    76  		if err != nil {
    77  			t.Fatal(err)
    78  		}
    79  		if !bytes.Equal(got, b) {
    80  			showDiffs(t, got, b)
    81  		}
    82  	})
    83  }
    84  
    85  // Fuzz test to check that if we can decompress some data,
    86  // so can zstd, and that we get the same result.
    87  func FuzzReverse(f *testing.F) {
    88  	zstd := findZstd(f)
    89  
    90  	for _, test := range tests {
    91  		f.Add([]byte(test.compressed))
    92  	}
    93  
    94  	// Set a hook to reject some cases where we don't match zstd.
    95  	fuzzing = true
    96  	defer func() { fuzzing = false }()
    97  
    98  	f.Fuzz(func(t *testing.T, b []byte) {
    99  		r := NewReader(bytes.NewReader(b))
   100  		goExp, goErr := io.ReadAll(r)
   101  
   102  		cmd := exec.Command(zstd, "-d")
   103  		cmd.Stdin = bytes.NewReader(b)
   104  		var uncompressed bytes.Buffer
   105  		cmd.Stdout = &uncompressed
   106  		cmd.Stderr = os.Stderr
   107  		zstdErr := cmd.Run()
   108  		zstdExp := uncompressed.Bytes()
   109  
   110  		if goErr == nil && zstdErr == nil {
   111  			if !bytes.Equal(zstdExp, goExp) {
   112  				showDiffs(t, zstdExp, goExp)
   113  			}
   114  		} else {
   115  			// Ideally we should check that this package and
   116  			// the zstd program both fail or both succeed,
   117  			// and that if they both fail one byte sequence
   118  			// is an exact prefix of the other.
   119  			// Actually trying this proved to be frustrating,
   120  			// as the zstd program appears to accept invalid
   121  			// byte sequences using rules that are difficult
   122  			// to determine.
   123  			// So we just check the prefix.
   124  
   125  			c := len(goExp)
   126  			if c > len(zstdExp) {
   127  				c = len(zstdExp)
   128  			}
   129  			goExp = goExp[:c]
   130  			zstdExp = zstdExp[:c]
   131  			if !bytes.Equal(goExp, zstdExp) {
   132  				t.Error("byte mismatch after error")
   133  				t.Logf("Go error: %v\n", goErr)
   134  				t.Logf("zstd error: %v\n", zstdErr)
   135  				showDiffs(t, zstdExp, goExp)
   136  			}
   137  		}
   138  	})
   139  }
   140  

View as plain text