1// Copyright 2016, Joe Tsai. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE.md file. 4 5// +build gofuzz 6 7package bzip2 8 9import ( 10 "bytes" 11 "errors" 12 "io/ioutil" 13 14 "github.com/dsnet/compress" 15 gbzip2 "github.com/dsnet/compress/bzip2" 16 cbzip2 "github.com/dsnet/compress/internal/cgo/bzip2" 17) 18 19func Fuzz(data []byte) int { 20 data, ok := testDecoders(data, true) 21 for i := 1; i <= 9; i++ { 22 testGoEncoder(data, i) 23 testCEncoder(data, i) 24 } 25 if ok { 26 return 1 // Favor valid inputs 27 } 28 return 0 29} 30 31// testDecoders tests that the input can be handled by both Go and C decoders. 32// This test does not panic if both decoders run into an error, since it 33// means that they both agree that the input is bad. 34// 35// If updateCRCs is set, then the Go bzip2 implementation will ignore all 36// checksum errors and manually adjust the checksum values before running the 37// C implementation. This hack drastically increases the probability that 38// gofuzz can generate a "valid" file. 39func testDecoders(data []byte, updateCRCs bool) ([]byte, bool) { 40 // Decompress using the Go decoder. 41 gr, err := gbzip2.NewReader(bytes.NewReader(data), nil) 42 if err != nil { 43 panic(err) 44 } 45 gb, gerr := ioutil.ReadAll(gr) 46 if err := gr.Close(); gerr == nil { 47 gerr = err 48 } else if gerr != nil && err == nil { 49 panic("nil on Close after non-nil error") 50 } 51 52 // Check or update the checksums. 53 if gerr == nil { 54 if updateCRCs { 55 data = gr.Checksums.Apply(data) 56 } else if !gr.Checksums.Verify(data) { 57 gerr = errors.New("bzip2: checksum error") 58 } 59 } 60 61 // Decompress using the C decoder. 62 cr := cbzip2.NewReader(bytes.NewReader(data)) 63 cb, cerr := ioutil.ReadAll(cr) 64 if err := cr.Close(); cerr == nil { 65 cerr = err 66 } else if cerr != nil && err == nil { 67 panic("nil on Close after non-nil error") 68 } 69 70 switch { 71 case gerr == nil && cerr == nil: 72 if !bytes.Equal(gb, cb) { 73 panic("mismatching bytes") 74 } 75 return gb, true 76 case gerr != nil && cerr == nil: 77 // Ignore deprecated errors since there are no plans to provide 78 // these features in the Go implementation. 79 if err, ok := gerr.(compress.Error); ok && err.IsDeprecated() { 80 return cb, false 81 } 82 panic(gerr) 83 case gerr == nil && cerr != nil: 84 panic(cerr) 85 default: 86 // Ensure that both gb and cb have the same common prefix. 87 if !bytes.HasPrefix(gb, cb) && !bytes.HasPrefix(cb, gb) { 88 panic("mismatching leading bytes") 89 } 90 return nil, false 91 } 92} 93 94// testGoEncoder encodes the input data with the Go encoder and then checks that 95// both the Go and C decoders can properly decompress the output. 96func testGoEncoder(data []byte, level int) { 97 // Compress using the Go encoder. 98 bb := new(bytes.Buffer) 99 gw, err := gbzip2.NewWriter(bb, &gbzip2.WriterConfig{Level: level}) 100 if err != nil { 101 panic(err) 102 } 103 defer gw.Close() 104 n, err := gw.Write(data) 105 if n != len(data) || err != nil { 106 panic(err) 107 } 108 if err := gw.Close(); err != nil { 109 panic(err) 110 } 111 112 // Decompress using both the Go and C decoders. 113 b, ok := testDecoders(bb.Bytes(), false) 114 if !ok { 115 panic("decoder error") 116 } 117 if !bytes.Equal(b, data) { 118 panic("mismatching bytes") 119 } 120} 121 122// testCEncoder encodes the input data with the C encoder and then checks that 123// both the Go and C decoders can properly decompress the output. 124func testCEncoder(data []byte, level int) { 125 // Compress using the C encoder. 126 bb := new(bytes.Buffer) 127 cw := cbzip2.NewWriter(bb, level) 128 defer cw.Close() 129 n, err := cw.Write(data) 130 if n != len(data) || err != nil { 131 panic(err) 132 } 133 if err := cw.Close(); err != nil { 134 panic(err) 135 } 136 137 // Decompress using both the Go and C decoders. 138 b, ok := testDecoders(bb.Bytes(), false) 139 if !ok { 140 panic("decoder error") 141 } 142 if !bytes.Equal(b, data) { 143 panic("mismatching bytes") 144 } 145} 146