vitess.io/vitess@v0.16.2/go/vt/vtgate/vindexes/fuzz.go (about)

     1  //go:build gofuzz
     2  // +build gofuzz
     3  
     4  /*
     5  Copyright 2021 The Vitess Authors.
     6  Licensed under the Apache License, Version 2.0 (the "License");
     7  you may not use this file except in compliance with the License.
     8  You may obtain a copy of the License at
     9      http://www.apache.org/licenses/LICENSE-2.0
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package vindexes
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"os"
    23  	"reflect"
    24  	"sync"
    25  	"testing"
    26  
    27  	fuzz "github.com/AdaLogics/go-fuzz-headers"
    28  
    29  	"vitess.io/vitess/go/sqltypes"
    30  	querypb "vitess.io/vitess/go/vt/proto/query"
    31  )
    32  
    33  var initter sync.Once
    34  
    35  func onceInit() {
    36  	testing.Init()
    37  }
    38  
    39  // All querypbTypes
    40  var querypbTypes = []querypb.Type{querypb.Type_NULL_TYPE,
    41  	querypb.Type_INT8,
    42  	querypb.Type_UINT8,
    43  	querypb.Type_INT16,
    44  	querypb.Type_UINT16,
    45  	querypb.Type_INT24,
    46  	querypb.Type_UINT24,
    47  	querypb.Type_INT32,
    48  	querypb.Type_UINT32,
    49  	querypb.Type_INT64,
    50  	querypb.Type_UINT64,
    51  	querypb.Type_FLOAT32,
    52  	querypb.Type_FLOAT64,
    53  	querypb.Type_TIMESTAMP,
    54  	querypb.Type_DATE,
    55  	querypb.Type_TIME,
    56  	querypb.Type_DATETIME,
    57  	querypb.Type_YEAR,
    58  	querypb.Type_DECIMAL,
    59  	querypb.Type_TEXT,
    60  	querypb.Type_BLOB,
    61  	querypb.Type_VARCHAR,
    62  	querypb.Type_VARBINARY,
    63  	querypb.Type_CHAR,
    64  	querypb.Type_BINARY,
    65  	querypb.Type_BIT,
    66  	querypb.Type_ENUM,
    67  	querypb.Type_SET,
    68  	querypb.Type_GEOMETRY,
    69  	querypb.Type_JSON,
    70  	querypb.Type_EXPRESSION}
    71  
    72  // All valid vindexes
    73  var availableVindexes = []string{"binary",
    74  	"unicode_loose_md5",
    75  	"binary_md5",
    76  	"lookup_hash",
    77  	"lookup_hash_unique",
    78  	"lookup",
    79  	"lookup_unique",
    80  	"lookup_unicodeloosemd5_hash",
    81  	"lookup_unicodeloosemd5_hash_unique",
    82  	"hash",
    83  	"region_experimental",
    84  	"consistent_lookup",
    85  	"consistent_lookup_unique",
    86  	"cfc",
    87  	"numeric",
    88  	"numeric_static_map",
    89  	"xxhash",
    90  	"unicode_loose_xxhash",
    91  	"reverse_bits",
    92  	"region_json",
    93  	"null"}
    94  
    95  // FuzzVindex implements the vindexes fuzzer
    96  func FuzzVindex(data []byte) int {
    97  	initter.Do(onceInit)
    98  	f := fuzz.NewConsumer(data)
    99  
   100  	// Write region map file
   101  	createdFile, err := writeRegionMapFile(f)
   102  	if err != nil {
   103  		return 0
   104  	}
   105  	defer os.Remove(createdFile)
   106  
   107  	// Choose type of vindex
   108  	index, err := f.GetInt()
   109  	if err != nil {
   110  		return 0
   111  	}
   112  	targetVindex := availableVindexes[index%len(availableVindexes)]
   113  
   114  	// Create params map
   115  	params := make(map[string]string)
   116  	err = f.FuzzMap(&params)
   117  	if err != nil {
   118  		return 0
   119  	}
   120  	params["region_map"] = createdFile
   121  
   122  	// Create the vindex
   123  	l, err := CreateVindex(targetVindex, targetVindex, params)
   124  	if err != nil {
   125  		return 0
   126  	}
   127  
   128  	// Create values to be passed to our targets
   129  	allValues, err := createValues(f)
   130  	if err != nil {
   131  		return 0
   132  	}
   133  
   134  	vc := &loggingVCursor{}
   135  
   136  	// Time to call the actual targets. THere are two:
   137  	// 1) Map()
   138  	// 2) Create()
   139  
   140  	// Target 1:
   141  	_, _ = Map(ctx, l, vc, allValues)
   142  
   143  	// Target 2:
   144  	s1 := reflect.TypeOf(l).String()
   145  	switch s1 {
   146  	case "*vindexes.ConsistentLookup", "*vindexes.LookupHash",
   147  		"*vindexes.LookupHashUnique", "*vindexes.LookupNonUnique",
   148  		"*vindexes.LookupUnique", "*vindexes.LookupUnicodeLooseMD5Hash",
   149  		"*vindexes.LookupUnicodeLooseMD5HashUnique", "*vindexes.clCommon",
   150  		"*vindexes.lookupInternal":
   151  		ksids, err := createKsids(f)
   152  		if err != nil {
   153  			return 0
   154  		}
   155  		_ = l.(Lookup).Create(ctx, vc, allValues, ksids, false /* ignoreMode */)
   156  	}
   157  	return 1
   158  }
   159  
   160  // Creates a slice of byte slices to use as ksids
   161  func createKsids(f *fuzz.ConsumeFuzzer) ([][]byte, error) {
   162  	// create ksids
   163  	noOfTotalKsids, err := f.GetInt()
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	ksids := make([][]byte, noOfTotalKsids%100)
   168  	for i := 0; i < noOfTotalKsids%100; i++ {
   169  		newBytes, err := f.GetBytes()
   170  		if err != nil {
   171  			return nil, err
   172  		}
   173  		ksids = append(ksids, newBytes)
   174  	}
   175  	return ksids, nil
   176  }
   177  
   178  // Checks if a byte slice is valid json
   179  func IsJSON(data []byte) bool {
   180  	var js json.RawMessage
   181  	return json.Unmarshal(data, &js) == nil
   182  }
   183  
   184  // Create and write the RegionMap file
   185  func writeRegionMapFile(f *fuzz.ConsumeFuzzer) (string, error) {
   186  	fileBytes, err := f.GetBytes()
   187  	if err != nil || !IsJSON(fileBytes) {
   188  		return "", fmt.Errorf("Could not get valid bytes")
   189  	}
   190  
   191  	createdFile, err := os.CreateTemp("", "tmpfile-")
   192  	if err != nil {
   193  		return "", err
   194  	}
   195  	defer createdFile.Close()
   196  
   197  	_, err = createdFile.Write(fileBytes)
   198  	if err != nil {
   199  		return "", err
   200  	}
   201  	return createdFile.Name(), nil
   202  
   203  }
   204  
   205  // Creates a slice of slices of sqltypes.Value
   206  func createValues(f *fuzz.ConsumeFuzzer) ([][]sqltypes.Value, error) {
   207  	noOfTotalValues, err := f.GetInt()
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	allValues := make([][]sqltypes.Value, 0)
   212  	for i := 0; i < noOfTotalValues%100; i++ {
   213  		noOfValues, err := f.GetInt()
   214  		if err != nil {
   215  			return nil, err
   216  		}
   217  		values := make([]sqltypes.Value, 0)
   218  		for i2 := 0; i2 < noOfValues%100; i2++ {
   219  			v, err := createValue(f)
   220  			if err != nil {
   221  				return nil, err
   222  			}
   223  			values = append(values, v)
   224  		}
   225  		allValues = append(allValues, values)
   226  	}
   227  	return allValues, nil
   228  }
   229  
   230  // Creates a single sqltypes.Value
   231  func createValue(f *fuzz.ConsumeFuzzer) (sqltypes.Value, error) {
   232  	typeIndex, err := f.GetInt()
   233  	if err != nil {
   234  		return sqltypes.Value{}, err
   235  	}
   236  
   237  	inputBytes, err := f.GetBytes()
   238  	if err != nil {
   239  		return sqltypes.Value{}, err
   240  	}
   241  
   242  	v := querypbTypes[typeIndex%len(querypbTypes)]
   243  	return sqltypes.NewValue(v, inputBytes)
   244  }