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  }