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 5package zstd 6 7import ( 8 "bytes" 9 "io" 10 "os" 11 "os/exec" 12 "testing" 13) 14 15// badStrings is some inputs that FuzzReader failed on earlier. 16var 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. 32func 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. 49func 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. 88func 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