istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/fuzz/util.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package fuzz 16 17 import ( 18 "bytes" 19 "fmt" 20 "strings" 21 "testing" 22 23 fuzzheaders "github.com/AdaLogics/go-fuzz-headers" 24 25 "istio.io/istio/pkg/test" 26 ) 27 28 const panicPrefix = "go-fuzz-skip: " 29 30 // Helper is a helper struct for fuzzing 31 type Helper struct { 32 cf *fuzzheaders.ConsumeFuzzer 33 t test.Failer 34 } 35 36 type Validator interface { 37 // FuzzValidate returns true if the current struct is valid for fuzzing. 38 FuzzValidate() bool 39 } 40 41 // Fuzz is a wrapper around: 42 // 43 // fuzz.BaseCases(f) 44 // f.Fuzz(func(...) { 45 // defer fuzz.Finalizer() 46 // } 47 // 48 // To avoid needing to call BaseCases and Finalize everywhere. 49 func Fuzz(f test.Fuzzer, ff func(fg Helper)) { 50 BaseCases(f) 51 f.Fuzz(func(t *testing.T, data []byte) { 52 defer Finalize() 53 fg := New(t, data) 54 ff(fg) 55 }) 56 } 57 58 // Finalize works around an issue in the oss-fuzz logic that doesn't allow using Skip() 59 // Instead, we send a panic which we handle and treat as skip. 60 // https://github.com/AdamKorcz/go-118-fuzz-build/issues/6 61 func Finalize() { 62 if r := recover(); r != nil { 63 if s, ok := r.(string); ok { 64 if strings.HasPrefix(s, panicPrefix) { 65 return 66 } 67 } 68 panic(r) 69 } 70 } 71 72 // New creates a new fuzz.Helper, capable of generating more complex types 73 func New(t test.Failer, data []byte) Helper { 74 return Helper{cf: fuzzheaders.NewConsumer(data), t: t} 75 } 76 77 // Struct generates a Struct. Validation patterns can be passed in - if any return false, the fuzz case is skipped. 78 // Additionally, if the T implements Validator, it will implicitly be used. 79 func Struct[T any](h Helper, validators ...func(T) bool) T { 80 d := new(T) 81 if err := h.cf.GenerateStruct(d); err != nil { 82 h.t.Skip(err.Error()) 83 } 84 r := *d 85 validate(h, validators, r) 86 return r 87 } 88 89 // Slice generates a slice of Structs 90 func Slice[T any](h Helper, count int, validators ...func(T) bool) []T { 91 if count < 0 { 92 // Make it easier to just pass fuzzer generated counts, typically with %max applied 93 count *= -1 94 } 95 res := make([]T, 0, count) 96 for i := 0; i < count; i++ { 97 d := new(T) 98 if err := h.cf.GenerateStruct(d); err != nil { 99 h.t.Skip(err.Error()) 100 } 101 r := *d 102 validate(h, validators, r) 103 res = append(res, r) 104 } 105 return res 106 } 107 108 func validate[T any](h Helper, validators []func(T) bool, r T) { 109 if fz, ok := any(r).(Validator); ok { 110 if !fz.FuzzValidate() { 111 h.t.Skip("struct didn't pass validator") 112 } 113 } 114 for i, v := range validators { 115 if !v(r) { 116 h.t.Skip(fmt.Sprintf("struct didn't pass validator %d", i)) 117 } 118 } 119 } 120 121 // BaseCases inserts a few trivial test cases to do a very brief sanity check of a test that relies on []byte inputs 122 func BaseCases(f test.Fuzzer) { 123 for _, c := range [][]byte{ 124 {}, 125 []byte("."), 126 bytes.Repeat([]byte("."), 1000), 127 } { 128 f.Add(c) 129 } 130 } 131 132 // T Returns the underlying test.Failer. Should be avoided where possible; in oss-fuzz many functions do not work. 133 func (h Helper) T() test.Failer { 134 return h.t 135 }