summaryrefslogtreecommitdiffstats
path: root/src/internal/zstd/fuzz_test.go
blob: 4b5c9961d87aee977ac654a6916d9c67402d8b7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package zstd

import (
	"bytes"
	"io"
	"os"
	"os/exec"
	"testing"
)

// badStrings is some inputs that FuzzReader failed on earlier.
var badStrings = []string{
	"(\xb5/\xfdd00,\x05\x00\xc4\x0400000000000000000000000000000000000000000000000000000000000000000000000000000 \xa07100000000000000000000000000000000000000000000000000000000000000000000000000aM\x8a2y0B\b",
	"(\xb5/\xfd00$\x05\x0020 00X70000a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
	"(\xb5/\xfd00$\x05\x0020 00B00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
	"(\xb5/\xfd00}\x00\x0020\x00\x9000000000000",
	"(\xb5/\xfd00}\x00\x00&0\x02\x830!000000000",
	"(\xb5/\xfd\x1002000$\x05\x0010\xcc0\xa8100000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
	"(\xb5/\xfd\x1002000$\x05\x0000\xcc0\xa8100d\x0000001000000000000000000000000000000000000000000000000000000000000000000000000\x000000000000000000000000000000000000000000000000000000000000000000000000000000",
	"(\xb5/\xfd001\x00\x0000000000000000000",
	"(\xb5/\xfd00\xec\x00\x00&@\x05\x05A7002\x02\x00\x02\x00\x02\x0000000000000000",
	"(\xb5/\xfd00\xec\x00\x00V@\x05\x0517002\x02\x00\x02\x00\x02\x0000000000000000",
	"\x50\x2a\x4d\x18\x02\x00\x00\x00",
}

// This is a simple fuzzer to see if the decompressor panics.
func FuzzReader(f *testing.F) {
	for _, test := range tests {
		f.Add([]byte(test.compressed))
	}
	for _, s := range badStrings {
		f.Add([]byte(s))
	}
	f.Fuzz(func(t *testing.T, b []byte) {
		r := NewReader(bytes.NewReader(b))
		io.Copy(io.Discard, r)
	})
}

// Fuzz test to verify that what we decompress is what we compress.
// This isn't a great fuzz test because the fuzzer can't efficiently
// explore the space of decompressor behavior, since it can't see
// what the compressor is doing. But it's better than nothing.
func FuzzDecompressor(f *testing.F) {
	zstd := findZstd(f)

	for _, test := range tests {
		f.Add([]byte(test.uncompressed))
	}

	// Add some larger data, as that has more interesting compression.
	f.Add(bytes.Repeat([]byte("abcdefghijklmnop"), 256))
	var buf bytes.Buffer
	for i := 0; i < 256; i++ {
		buf.WriteByte(byte(i))
	}
	f.Add(bytes.Repeat(buf.Bytes(), 64))
	f.Add(bigData(f))

	f.Fuzz(func(t *testing.T, b []byte) {
		cmd := exec.Command(zstd, "-z")
		cmd.Stdin = bytes.NewReader(b)
		var compressed bytes.Buffer
		cmd.Stdout = &compressed
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			t.Errorf("running zstd failed: %v", err)
		}

		r := NewReader(bytes.NewReader(compressed.Bytes()))
		got, err := io.ReadAll(r)
		if err != nil {
			t.Fatal(err)
		}
		if !bytes.Equal(got, b) {
			showDiffs(t, got, b)
		}
	})
}

// Fuzz test to check that if we can decompress some data,
// so can zstd, and that we get the same result.
func FuzzReverse(f *testing.F) {
	zstd := findZstd(f)

	for _, test := range tests {
		f.Add([]byte(test.compressed))
	}

	// Set a hook to reject some cases where we don't match zstd.
	fuzzing = true
	defer func() { fuzzing = false }()

	f.Fuzz(func(t *testing.T, b []byte) {
		r := NewReader(bytes.NewReader(b))
		goExp, goErr := io.ReadAll(r)

		cmd := exec.Command(zstd, "-d")
		cmd.Stdin = bytes.NewReader(b)
		var uncompressed bytes.Buffer
		cmd.Stdout = &uncompressed
		cmd.Stderr = os.Stderr
		zstdErr := cmd.Run()
		zstdExp := uncompressed.Bytes()

		if goErr == nil && zstdErr == nil {
			if !bytes.Equal(zstdExp, goExp) {
				showDiffs(t, zstdExp, goExp)
			}
		} else {
			// Ideally we should check that this package and
			// the zstd program both fail or both succeed,
			// and that if they both fail one byte sequence
			// is an exact prefix of the other.
			// Actually trying this proved to be frustrating,
			// as the zstd program appears to accept invalid
			// byte sequences using rules that are difficult
			// to determine.
			// So we just check the prefix.

			c := len(goExp)
			if c > len(zstdExp) {
				c = len(zstdExp)
			}
			goExp = goExp[:c]
			zstdExp = zstdExp[:c]
			if !bytes.Equal(goExp, zstdExp) {
				t.Error("byte mismatch after error")
				t.Logf("Go error: %v\n", goErr)
				t.Logf("zstd error: %v\n", zstdErr)
				showDiffs(t, zstdExp, goExp)
			}
		}
	})
}